К содержанию
Первый Коммит

Модуль 1

Что такое MCP и первый сервер

  • Зачем нужен протокол
  • Хост, клиент, сервер
  • Инструменты, ресурсы, промпты
  • JSON-RPC и транспорты
  • Установка окружения
  • MCP Inspector

Этот курс о том, как научить языковую модель выполнять действия за пределами диалога: читать файлы, обращаться к базе данных, запрашивать погоду, считать по формулам предметной области. Всё это делается через MCP — Model Context Protocol, открытый протокол, по которому модель обращается к внешнему коду.

Важно сразу отметить: чтобы писать MCP-серверы, не нужно разбираться в нейросетях, обучении и математике. MCP-сервер — это обычная Python-программа с обычными функциями. Разработчик пишет функцию, описывает словами, что она делает, и модель начинает ею пользоваться. Вся «магия» происходит на стороне модели, а на стороне сервера — обычный код.

Что нужно знать заранее: основы Python (функции, аргументы и их типы, исключения, установка пакетов) и умение работать в терминале — переход в нужную папку, запуск скрипта, чтение сообщения об ошибке. Python в этом наборе курсов отдельно не преподаётся: он изучается в рамках профессионального модуля по разработке кода для ИИ.

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

#1.1 Зачем понадобился отдельный протокол

Языковая модель сама по себе умеет только одно: получать текст и выдавать текст. Она не может открыть файл на вашем диске, узнать сегодняшнюю дату или посмотреть остаток на складе. Всё, что она «знает», зафиксировано на момент обучения и не обновляется.

Отсюда очевидное желание: дать модели инструменты. При вопросе о погоде она обратится в погодный сервис, при вопросе о заказах — в базу данных. Технически это давно возможно: модели умеют вызывать функции (механизм так и называется — function calling). Модель получает список доступных функций с описаниями и, если задача того требует, отвечает не текстом, а просьбой «вызови функцию get_weather с аргументом город равно Омск».

Проблема была в другом: каждый производитель делал этот механизм по-своему. Интеграция с базой данных, написанная для одного чат-клиента, не работала в другом. При смене модели обвязку приходилось переписывать. Десять инструментов и пять клиентов означали пятьдесят несовместимых фрагментов кода.

MCP решает эту задачу. Это единый протокол общения между тем, кто предоставляет возможности (ваш сервер), и тем, кто ими пользуется (приложение с моделью). Сервер, написанный один раз, работает в любом клиенте с поддержкой MCP и с любой моделью. Аналогия: USB. Раньше у каждого устройства был свой разъём, теперь есть общий стандарт, и мышь подключается к любому ноутбуку.

#1.2 Архитектура: хост, клиент, сервер

В MCP три роли, и путаница между ними — самая частая ошибка новичка.

Хост (host) — приложение, которым пользуется человек. Это редактор VS Code с ИИ-плагином, десктопное приложение чат-бота, собственный веб-интерфейс. Хост общается с моделью и решает, что показать пользователю.

Клиент (client) — часть внутри хоста, которая работает по протоколу MCP. На каждый подключённый сервер хост создаёт отдельный клиент. Обычно писать клиент не нужно: он встроен в редактор или приложение. Собственный клиент пишется только для тестов — этим вы и займётесь в практике этого модуля.

Сервер (server) — ваша программа. Она объявляет свои возможности и выполняет запросы. Её написанию посвящён весь курс.

Схема к модулю «Что такое MCP и первый сервер»

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

#1.3 Три примитива: инструменты, ресурсы, промпты

Сервер может предоставить три разных вида возможностей. Разница между ними — не в технике, а в том, кто принимает решение об использовании.

Инструменты (tools) — действия, которые вызывает модель по своему усмотрению. Посчитать, записать, отправить, найти. Это активная сущность: модель сама решает, когда вызвать функцию.

Ресурсы (resources) — данные, которые сервер отдаёт по запросу, чтобы они попали в контекст модели. Содержимое файла, текст документации, свежий лог. Это пассивная сущность: ресурс не выполняет действий, а только отдаёт содержимое. Решение подключить ресурс обычно принимает хост или человек, а не модель.

Промпты (prompts) — заготовленные шаблоны диалога, которые предлагаются пользователю. Например, «проанализируй этот лог как системный администратор». Их выбирает человек в интерфейсе хоста.

Практическое правило выбора: если возможность меняет состояние системы или выполняет вычисление — это инструмент. Если она отдаёт фрагмент данных для чтения — ресурс. Если это готовая формулировка задачи для человека — промпт.

#1.4 JSON-RPC и транспорты

Под капотом MCP использует JSON-RPC — простой и старый способ вызывать удалённые процедуры. Клиент отправляет объект JSON с именем метода и параметрами, сервер отвечает объектом JSON с результатом.

Запрос на вызов инструмента выглядит примерно так:

JSON
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "calculate_bmi",
    "arguments": { "weight_kg": 70, "height_m": 1.75 }
  }
}

А ответ — так:

JSON
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [ { "type": "text", "text": "ИМТ = 22.9" } ],
    "structuredContent": { "result": "ИМТ = 22.9" }
  }
}

Обратите внимание на поле id: оно связывает ответ с запросом. Это пригодится в отладке, когда запросов много и они идут вперемешку.

Теперь о транспорте — способе доставки этих сообщений. Стандартных транспорта два.

Стандартный ввод-вывод (stdio) — хост запускает ваш сервер как обычный процесс и общается с ним через его стандартные потоки ввода и вывода. Сервер работает на той же машине, никаких портов, никакой сети. Это основной режим для локальных инструментов, и весь курс до модуля 3 построен на нём.

Streamable HTTP — сервер поднимается как настоящий веб-сервис, слушает порт и может обслуживать удалённых клиентов. Нужен, когда сервер находится на другой машине или должен обслуживать нескольких пользователей.

Из устройства stdio вытекает важное следствие: в этом режиме стандартный вывод занят протоколом, и посторонний текст в нём нарушает обмен. Классический совет звучит так: никогда не используйте print, выводите отладочные сообщения в поток ошибок.

Во второй версии библиотека частично страхует от такой ошибки. При запуске транспорта она забирает себе дескриптор стандартного вывода, а всё, что программа печатает обычным способом, перенаправляет в поток ошибок. Поэтому print внутри инструмента протокол не нарушает — сообщение всё равно уходит в поток ошибок. А вот print, выполненный до запуска сервера, то есть в теле модуля при импорте, попадёт в настоящий стандартный вывод и может нарушить обмен: подробный разбор того, когда именно посторонний текст доберётся до канала протокола, ждёт в модуле 3 вместе с экспериментом.

#1.5 Готовим окружение

Проверьте, что установлен Python 3.14.

Windows (PowerShell):

Код
py --version

macOS и Linux:

Терминал
python3 --version

Если команда не найдена, установите Python с сайта python.org. На Windows при установке обязательно поставьте галочку «Add python.exe to PATH» — без неё терминал не найдёт интерпретатор, и это самая частая причина, по которой у разработчика «ничего не работает».

Дальше создаём папку проекта и виртуальное окружение. Виртуальное окружение — это отдельная папка с копией интерпретатора и своим набором пакетов: так библиотеки одного проекта не конфликтуют с библиотеками другого.

Windows (PowerShell):

Терминал
mkdir mcp-course
cd mcp-course
py -m venv .venv
.venv\Scripts\Activate.ps1

macOS и Linux:

Терминал
mkdir mcp-course
cd mcp-course
python3 -m venv .venv
source .venv/bin/activate

После активации в начале строки терминала появится префикс с именем окружения. Если Windows PowerShell отказывается выполнять скрипт активации и сообщает о политике выполнения, разрешите запуск скриптов для текущего пользователя командой ниже и подтвердите изменение:

Терминал
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

Другой путь — запустить .venv\Scripts\activate.bat в обычной командной строке cmd.

Теперь ставим SDK. Сам SDK закрепить просто — командой pip install "mcp[cli]==2.1.1", — но вместе с ним приезжают ещё почти четыре десятка библиотек, а их версии эта команда не закрепляет: pip возьмёт самые свежие на день установки. Через месяц у вас окажется не тот набор, на котором проверен курс, и то, что вы увидите на экране, начнёт расходиться с текстом модулей. Поэтому все пакеты курса перечислены с точными версиями в файле requirements.txt. Создайте его в папке проекта:

Код
annotated-doc==0.0.5
annotated-types==0.8.0
anyio==4.15.1
attrs==26.1.0
cffi==2.1.1
click==8.5.0
colorama==0.4.6 ; sys_platform == 'win32'
cryptography==48.0.1
h11==0.16.0
httpcore2==2.13.1
httpx2==2.13.1
idna==3.20
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
markdown-it-py==4.2.0
mcp[cli]==2.1.1
mcp-types==2.1.1
mdurl==0.1.2
opentelemetry-api==1.45.0
pycparser==3.0
pydantic==2.13.5
pydantic-core==2.46.5
pygments==2.21.0
pyjwt==2.15.1
python-dotenv==1.2.3
python-multipart==0.0.32
pywin32==312 ; sys_platform == 'win32'
referencing==0.37.0
rich==15.0.0
rpds-py==2026.6.3
shellingham==1.5.4
sse-starlette==3.5.0
starlette==1.7.0
truststore==0.10.4
typer==0.27.2
typing-extensions==4.16.0
typing-inspection==0.4.4
uvicorn==0.54.0

И поставьте всё одной командой, одинаковой на всех системах:

Терминал
pip install -r requirements.txt

Две строки с пометкой sys_platform == 'win32' нужны только на Windows, на macOS и Linux pip пропустит их сам. Файл requirements.txt понадобится в каждом модуле курса: создавая папку нового проекта, копируйте его туда и ставьте пакеты той же командой. Пакет, который вы добавите в него сами, тоже записывайте с точной версией через ==, иначе его версию снова выберет pip.

Квадратные скобки с cli означают, что вместе с библиотекой ставится утилита командной строки. Точные версии страхуют от ситуации, когда через год выйдет третья мажорная версия SDK или новая версия одной из его зависимостей и учебный проект перестанет собираться.

Проверяем установку:

Код
mcp version

Пример вывода: MCP version 2.1.1.

После активации команда python одинакова во всех трёх системах: активация ставит интерпретатор окружения первым в PATH. Поэтому дальше в курсе команды запуска пишутся одной строкой, без отдельного варианта для Windows. Если скрипт, запущенный командой python, падает с ошибкой No module named 'mcp', окружение в этом окне не активировано. Окружение нужно активировать в каждом новом окне терминала, в том числе встроенного в VS Code. А если на Windows вместо запуска открывается Microsoft Store, Python установлен без галочки «Add python.exe to PATH».

Теперь поставим ещё один инструмент — менеджер пакетов uv. Отладчик, который используется в следующем разделе и во всех дальнейших модулях, запускает сервер через uv. Без uv отладчик откроется, но подключиться к серверу не сможет.

Windows (PowerShell):

Терминал
pip install uv==0.12.5

macOS и Linux — так же, в активированном окружении:

Терминал
pip install uv==0.12.5

Проверяем:

Терминал
uv --version

#1.6 Первый сервер

Создайте файл basic_tools.py со следующим содержимым.

Python
from datetime import datetime

from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError

mcp = MCPServer("Учебный сервер")


@mcp.tool()
def calculate_bmi(weight_kg: float, height_m: float) -> str:
    """Рассчитать индекс массы тела по весу в килограммах и росту в метрах."""
    if height_m <= 0:
        raise ToolError("Рост должен быть больше нуля")
    bmi = weight_kg / (height_m ** 2)
    return f"ИМТ = {bmi:.1f}"


@mcp.tool()
def get_system_time() -> str:
    """Вернуть текущую дату и время на машине, где запущен сервер."""
    return datetime.now().strftime("%d.%m.%Y %H:%M:%S")


@mcp.tool()
def text_length(text: str) -> int:
    """Посчитать количество символов в строке."""
    return len(text)


if __name__ == "__main__":
    mcp.run()

Разберём этот код по частям.

Объект MCPServer, созданный с именем, — это и есть сервер. Имя увидит пользователь в интерфейсе хоста при выборе подключаемых серверов, поэтому оно должно быть осмысленным.

Декоратор @mcp.tool() регистрирует функцию как инструмент. Скобки обязательны: это декоратор с параметрами, и вызывать его нужно, даже когда параметров нет.

Аннотации типов у аргументов обязательны, так как библиотека по ним строит схему инструмента, которую увидит модель: weight_kg объявлен как float, значит модель поймёт, что сюда передаётся число, а не строка, и библиотека проверит это до вызова функции.

Строка документации (docstring) — это описание инструмента для модели. Модель читает её, решая, подходит ли инструмент под задачу пользователя. Пустой или невнятный docstring означает, что инструмент не будет вызываться. В модуле 4 мы поставим эксперимент и увидим это на практике.

Вызов mcp.run() в конце запускает сервер. По умолчанию используется транспорт stdio, то есть сервер ждёт сообщений в стандартном вводе.

Остановить такой сервер проще всего сочетанием Ctrl+D: оно означает «ввод закончился», сервер понимает это как штатное завершение и закрывается молча с первого раза. Ctrl+C тоже сработает, но не сразу: на первое нажатие сервер не реагирует вовсе, со второго печатает простыню трассировки с KeyboardInterrupt и всё ещё живёт, и закрывается только с третьего. Трассировка здесь не ошибка вашего кода — так выглядит прерывание программы, которая ждёт ввода в нескольких потоках сразу. Сочетанием Ctrl+C останавливают и отладчик, запущенный командой mcp dev: он тоже держит терминал занятым, пока работает.

#1.7 Отладка без ИИ: MCP Inspector

MCP Inspector — графический отладчик, который сам выполняет роль клиента: подключается к серверу, показывает список инструментов и позволяет вызвать любой из них вручную и увидеть запрос и ответ в исходном виде.

Он написан на JavaScript и запускается через npx, поэтому потребуется установленный Node.js версии 22.19 или новее — проще всего поставить текущий LTS. На более старом Node команда не сообщит об ошибке: npx возьмёт последнюю версию отладчика, которая на нём запускается, а это ветка 0.x с совсем другим устройством окна — описания этого раздела к ней не подойдут.

Проверить, есть ли Node:

Терминал
node --version

Если команда не найдена, поставьте Node.js: на Windows и macOS — установщиком LTS-версии с сайта nodejs.org, на Linux — пакетом из репозитория дистрибутива (в Debian и Ubuntu это nodejs вместе с npm). После установки закройте терминал и откройте заново, иначе он не увидит новую команду — так же, как с Python в разделе 1.5.

Теперь поставьте сам отладчик. Команда одинакова во всех трёх системах:

Терминал
npm install -g @modelcontextprotocol/inspector@2.10.1

Ключ -g означает глобальную установку: пакет попадает не в папку проекта, а в общее хранилище, и в системе появляется команда mcp-inspector, доступная из любого каталога. Если установка завершится отказом в доступе, повторите её через sudo — так бывает на Linux и macOS, когда Node поставлен системно, а не менеджером версий.

Приписка @2 к имени пакета фиксирует версию. Свежий npm подбирает версию пакета под установленный Node, и без этой приписки на слишком старом Node он поставит старую ветку отладчика — ту самую, с другим устройством окна. Если приписка есть, npm ставит нужную ветку и предупреждает: печатает EBADENGINE и называет требуемую версию Node. Увидев такое предупреждение, обновите Node, а не пытайтесь работать с установленным.

Проверить, что получилось:

Терминал
npm ls -g @modelcontextprotocol/inspector

Запуск из папки проекта при активированном виртуальном окружении:

Код
mcp dev basic_tools.py

Команда запустит сервер и откроет Inspector в браузере. Убедитесь, что сервер подключён (в списке серверов у него включён переключатель), и перейдите в раздел инструментов — там появятся три инструмента. В списке видны только имена; описание, взятое из docstring, показывается в правой панели, когда инструмент выбран.

#Режим веб-инспектора

Выберите calculate_bmi, введите вес 70 и рост 1.75 и запустите вызов. В ответе увидите текст «ИМТ = 22.9», а рядом — структурированный результат вида result равно ИМТ = 22.9. Второе поле библиотека формирует автоматически из возвращаемого значения функции.

Теперь намеренно передадим неверное значение: введите рост 0 и запустите снова. В ответ придёт результат с признаком ошибки и текстом «Error executing tool calculate_bmi: Рост должен быть больше нуля».

Это принципиально важный момент, определяющий весь стиль написания инструментов. Исключение внутри функции не завершает работу сервера: библиотека перехватывает его, помечает результат как ошибочный и передаёт текст исключения наверх. Значит, текст исключения читает модель и по нему решает, что делать дальше. Поэтому сообщения об ошибках в MCP-сервере пишут не для программиста, а для читателя, который попытается исправиться сам. Подробно этим займёмся в модуле 7.

#Консольный режим

У отладчика есть режим, которому браузер не нужен: в этом режиме отладчик сам запускает сервер, сам выполняет один запрос и печатает ответ прямо в терминал.

Список инструментов, из папки проекта при активированном виртуальном окружении:

Код
mcp-inspector --cli python basic_tools.py --method tools/list

В ответ придёт JSON со всеми тремя инструментами: у каждого имя, описание из строки документации и схема аргументов. Ровно это и увидит модель.

Терминал
npx @modelcontextprotocol/inspector@2.10.1 --cli python basic_tools.py --method tools/list

При первом запуске npx покажет имя пакета и запросит подтверждение — ответьте y.

Ключи после имени пакета читает отладчик, а не npx, поэтому любую команду этого раздела можно записать в такой форме — достаточно заменить mcp-inspector на npx @modelcontextprotocol/inspector@2.10.1.

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

Код
mcp-inspector --cli python basic_tools.py --method tools/call --tool-name calculate_bmi --tool-arg weight_kg=70 --tool-arg height_m=1.75

Ответ придёт в исходном виде, без оформления:

JSON
{
  "content": [
    {
      "type": "text",
      "text": "ИМТ = 22.9"
    }
  ],
  "structuredContent": {
    "result": "ИМТ = 22.9"
  },
  "isError": false
}

Здесь виден формат ответа целиком: блок content с текстом для человека, structuredContent с разобранным значением и признак ошибки. Окно веб-инспектора покажет то же самое, только с красивым форматированием.

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

#1.8 Практика: свой сервер и собственный клиент

В этой практике вы соберёте сервер с нуля и научитесь проверять его двумя способами: вручную через Inspector (в окне или в режиме CLI) и автоматически — собственным клиентом.

#Задание 1.1 — сервер учебной группы

Соберите сервер, который помогает старосте группы.

Шаг 1. Создайте папку и виртуальное окружение по инструкции раздела 1.5, установите mcp[cli].

Шаг 2. Создайте файл group_tools.py и объявите в нём сервер с именем «Помощник старосты».

Шаг 3. Реализуйте три инструмента, каждый с аннотациями типов и внятным docstring:

  • average_grade — принимает список оценок в аргументе grades (тип list[int]) и возвращает среднее с точностью до одного знака
  • lessons_left — принимает аргументы current_lesson и total_lessons (оба int), возвращает, сколько пар осталось
  • format_fio — принимает аргументы surname, name и patronymic (все три типа str) и возвращает строку вида «Иванов И. И.»

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

Шаг 4. В каждом инструменте предусмотрите проверку входных данных и бросайте ToolError с понятным текстом на русском языке. Пустой список оценок, отрицательный номер пары, пустая фамилия — всё это должно давать осмысленное сообщение, а не завершение с непонятной ошибкой.

Готовый сервер целиком выглядит так. Создайте его самостоятельно, а этот листинг используйте для сверки:

Python
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError

mcp = MCPServer("Помощник старосты")


@mcp.tool()
def average_grade(grades: list[int]) -> str:
    """Посчитать средний балл по списку оценок. Возвращает среднее с одним знаком после запятой."""
    if not grades:
        raise ToolError("Список оценок пуст. Передайте хотя бы одну оценку.")
    for grade in grades:
        if grade < 2 or grade > 5:
            raise ToolError(f"Оценка {grade} вне допустимого диапазона. Оценки бывают от 2 до 5.")
    return f"Средний балл: {sum(grades) / len(grades):.1f}"


@mcp.tool()
def lessons_left(current_lesson: int, total_lessons: int) -> str:
    """Посчитать, сколько пар осталось до конца учебного дня."""
    if current_lesson < 1:
        raise ToolError("Номер текущей пары должен быть не меньше 1.")
    if total_lessons < 1:
        raise ToolError("Всего пар за день должно быть не меньше 1.")
    if current_lesson > total_lessons:
        raise ToolError(
            f"Текущая пара {current_lesson} больше общего числа пар {total_lessons}. "
            "Проверьте расписание."
        )
    left = total_lessons - current_lesson
    return f"Осталось пар: {left}" if left else "Это последняя пара сегодня"


@mcp.tool()
def format_fio(surname: str, name: str, patronymic: str) -> str:
    """Сократить фамилию, имя и отчество до вида «Иванов И. И.» для списка группы."""
    if not surname.strip():
        raise ToolError("Фамилия не может быть пустой.")
    if not name.strip():
        raise ToolError("Имя не может быть пустым.")
    short = f"{surname.strip()} {name.strip()[0].upper()}."
    if patronymic.strip():
        short += f" {patronymic.strip()[0].upper()}."
    return short


if __name__ == "__main__":
    mcp.run()

Шаг 5. Запустите mcp dev group_tools.py (либо через mcp-inspector, если вы используете консоль), подключитесь и вызовите каждый инструмент дважды: с корректными данными и с заведомо неверными. Список оценок вводится в форму в виде JSON, с квадратными скобками: [5, 4, 5, 3]. Перечисление через запятую отладчик пропустит, отправив его обычной строкой, и откажет уже ваш сервер — сообщением проверки типов вида Input should be a valid list. Полезно увидеть и это: отказ пришёл не от формы, а с той стороны. Убедитесь, что во втором случае возвращается признак ошибки и ваш текст.

#Задание 1.2 — клиент вместо ручной проверки

Ручная проверка в Inspector после каждой правки утомительна. Напишем клиент, который выполнит её сам.

Шаг 1. Создайте файл check.py рядом с сервером.

Шаг 2. Наберите следующий код.

Python
import asyncio

from mcp.client import Client

from group_tools import mcp as server


async def main():
    async with Client(server) as client:
        tools = await client.list_tools()
        print("Инструменты:", [t.name for t in tools.tools])

        result = await client.call_tool("average_grade", {"grades": [5, 4, 5, 3]})
        print("Среднее:", result.content[0].text)
        print("Структурированно:", result.structured_content)

        bad = await client.call_tool("average_grade", {"grades": []})
        print("Признак ошибки:", bad.is_error)
        print("Текст:", bad.content[0].text)


asyncio.run(main())

Здесь впервые появились три незнакомые конструкции — разберём их, потому что дальше они встретятся не раз.

Слово async перед def означает, что функция асинхронная: она умеет останавливаться в середине, отдавать управление и продолжать работу позже, когда придёт нужный результат. Обычная функция так не умеет: она занимает программу целиком до завершения вычисления.

Слово await ставится перед вызовом, результата которого нужно дождаться. Оно читается как «подожди здесь, а пока можно заняться другим». Любое обращение к серверу занимает время, поэтому все вызовы клиента идут через await. Пропуск await — типичная ошибка: вместо ответа возвращается объект-обещание, а следом возникает непонятная ошибка.

Конструкция async with Client(server) as client открывает соединение и гарантированно закрывает его на выходе из блока, даже при возникновении ошибки внутри. А asyncio.run(main()) в последней строке запускает всю конструкцию: обычный Python-код не вызывает асинхронные функции напрямую, ему нужен запускающий механизм.

Шаг 3. Запустите проверку командой python check.py и убедитесь, что видите список инструментов, корректный расчёт и осмысленное сообщение об ошибке на пустом списке.

Шаг 4. Дополните клиент вызовами двух оставшихся инструментов.

Обратите внимание на две особенности этого кода. Клиент принимает объект сервера напрямую, без запуска отдельного процесса — вызовы идут в памяти, поэтому проверка выполняется мгновенно, и её удобно запускать после каждой правки. Поля результата пишутся через подчёркивание: structured_content и is_error. В примерах из интернета встречается запись structuredContent и isError — это стиль версии 1.x, в версии 2 такой Python-код падает с AttributeError.

#Задание 1.3 — читаем протокол

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

Шаг 1. Найдите, каким методом выполнен вызов, и убедитесь, что это tools/call. Учтите: отладчик показывает не весь конверт JSON-RPC, а его части по отдельности — имя метода вынесено меткой над записью, а в панели параметров находится только содержимое поля params. Полный вид конверта приведён в разделе 1.4, сверьтесь с ним.

Шаг 2. Найдите в ответе поле id. В запросе отладчик его не показывает. Зато в параметрах вызова есть служебное поле с номером; правда, служит оно другому механизму, а запрос с ответом связывает именно id — в этом вызове их значения совпали, потому что оба берутся из общего счётчика. Подумайте, зачем в протоколе нужен такой номер, если запрос и ответ идут по одному соединению.

Шаг 3. Найдите в ответе поле content и подумайте, почему содержимое лежит в массиве, а не отдельной строкой. Подсказка: инструмент может вернуть несколько фрагментов сразу, и не обязательно текстовых.

#Итоги модуля 1

  • MCP — открытый протокол, единый способ подключить внешние функции к языковой модели; он избавляет от необходимости писать отдельную интеграцию под каждую пару «клиент — инструмент»
  • Писать MCP-серверы можно без знаний о нейросетях: сервер — обычная Python-программа, а решение о вызове функции принимает модель на другой стороне
  • В системе три роли: хост (приложение пользователя), клиент (работает по протоколу, встроен в хост) и сервер (ваш код); сервер никогда не общается с моделью напрямую, поэтому его можно отлаживать без неё
  • Сервер предоставляет три вида возможностей: инструменты (активные действия, вызывает модель), ресурсы (пассивные данные для чтения) и промпты (шаблоны диалога для человека)
  • Много мелких инструментов лучше не использовать: чем длиннее список, тем хуже модель выбирает нужный — данные для чтения правильнее отдавать ресурсом
  • Сообщения записываются в формате JSON-RPC, а доставляются транспортом, и это разные вещи: стандартных транспорта два — stdio для локальных серверов и Streamable HTTP для сетевых, а SSE устарел с ревизии 2025-03-26
  • В режиме stdio стандартный вывод занят протоколом; библиотека версии 2 сама уводит обычный print в поток ошибок, но печать до запуска сервера, в теле модуля, нарушает обмен (когда именно — разбирается в модуле 3)
  • Пакеты курса ставятся командой pip install -r requirements.txt из файла, где у каждой библиотеки точная версия: команда pip install "mcp[cli]==2.1.1" закрепила бы только сам SDK, а его зависимости поставила бы самыми свежими; ставя SDK отдельной командой, берите имя в кавычки — без них zsh на macOS примет квадратные скобки за шаблон имён файлов и ничего не установит
  • Главный класс версии 2 называется MCPServer, а модуля mcp.server.fastmcp из старых туториалов в ней не существует физически, поэтому строка импорта из большинства статей в интернете падает с ModuleNotFoundError
  • Инструмент объявляется декоратором @mcp.tool(); аннотации типов задают схему аргументов, а docstring служит описанием, по которому модель решает, вызывать ли функцию
  • Исключение внутри инструмента не завершает работу сервера: библиотека возвращает признак ошибки и текст исключения, который читает модель, поэтому сообщения пишут понятными для модели, а не для того, кто отлаживает код
  • MCP Inspector запускается командой mcp dev и позволяет вызвать любой инструмент вручную и увидеть запрос и ответ в исходном виде; проверять новый инструмент нужно в нём до подключения модели
  • Свой клиент через объект Client принимает сервер напрямую и работает в памяти, поэтому автоматическая проверка проходит мгновенно; поля результата пишутся в стиле structured_content и is_error

#Домашнее задание

Задания выполняются в папке, отдельной от практики. Сквозная тема — сервер для школьной библиотеки, к нему мы будем возвращаться в следующих модулях.

  • Создайте проект library-server с виртуальным окружением и установленным mcp[cli]; объявите сервер с именем «Библиотека» и запустите его в веб-инспекторе или в режиме CLI. Сервер должен показывать пустой список инструментов
  • Реализуйте инструмент days_overdue: принимает дату выдачи книги и срок возврата в днях, возвращает количество дней просрочки на сегодня (ноль, если срок не вышел); дату принимайте строкой в формате ДД.ММ.ГГГГ и разбирайте через datetime.strptime
  • Реализуйте инструмент fine_amount, считающий штраф: первые три дня просрочки бесплатны, далее десять рублей за день, но не более пятисот рублей суммарно; на некорректные входные данные бросайте ToolError с понятным текстом
  • Напишите клиент check.py, проверяющий оба инструмента минимум на четырёх наборах данных: без просрочки, в льготном периоде, с обычным штрафом и с превышением потолка — по строке на проверку с ожидаемым и полученным значением
  • Подумайте, какие три инструмента вы бы добавили серверу дальше и почему каждый должен быть именно инструментом, а не ресурсом; приведите один пример возможности, которую правильнее сделать ресурсом
  • (со звёздочкой) Выясните на практике, что произойдёт, если объявить два инструмента с одинаковым именем, и что будет, если у инструмента вовсе не окажется docstring; зафиксируйте поведение библиотеки и объясните, почему второй случай опаснее первого, хотя ошибки при запуске не возникает

Войдите, чтобы отмечать пройденные модули и видеть прогресс по курсу.

Введите запрос: название модуля, тема или раздел.

Стрелки — выбор, Enter — открыть. Ищет по названиям модулей, темам и разделам.