MCP-сервер превращает Python-функцию в инструмент, который ИИ может обнаружить и вызвать сам. Для первого рабочего примера не нужен веб-сервер, база данных или ручная JSON-схема. Мы опишем функцию с типами, откроем её в MCP Inspector и увидим структурированный ответ.
Представьте, что вы просите ИИ оценить прогресс по курсу. Без инструмента модель рассуждает по тексту и может ошибиться в арифметике. С инструментом она передаёт два числа функции, получает точный процент и формулирует ответ. Модель выбирает момент вызова, но расчёт выполняет Python. Так MCP подключает к ассистентам файлы, API и внутренние сервисы по общему протоколу.
Создаём обычную функцию с понятным именем, docstring и типами аргументов.
Декоратор tool сообщает MCP-клиенту, что функцию разрешено вызывать.
Inspector показывает схему аргументов, отправляет вызов и выводит ответ.
Что происходит между ИИ и Python-функцией
В цепочке есть три роли. Host является приложением, в котором пользователь общается с моделью. Внутри host работает MCP-клиент. Он подключается к вашему серверу, запрашивает список возможностей и передаёт вызовы. Сам сервер не разговаривает с моделью и не решает, когда нужен инструмент. Он принимает уже сформированные аргументы, запускает функцию и возвращает результат. В примере сервер называется Study Progress, а инструмент estimate_progress принимает solved и total. Такой проект легко проверить, потому что расчёт детерминированный: одинаковые числа всегда дают одинаковый процент.
| Роль | Что делает | Чего не делает |
|---|---|---|
| Host | Показывает чат и запускает клиентов | Не хранит логику вашей функции |
| MCP-клиент | Узнаёт инструменты и передаёт вызовы | Не вычисляет процент сам |
| MCP-сервер | Проверяет аргументы и запускает Python | Не выбирает цель пользователя |
| Инструмент | Возвращает точный структурированный результат | Не получает лишний доступ автоматически |
MCP не делает функцию умнее и не прячет её поведение. Он описывает безопасный договор: имя, назначение, типы входных данных и формат ответа. Чем уже этот договор, тем проще тестировать сервер и понимать действия модели.

Запрос проходит четыре понятных слоя. Результат возвращается по той же цепочке
Создаём MCP-сервер и запускаем Inspector
Установите uv, создайте каталог командой uv init mcp-study, перейдите в него и добавьте SDK: uv add "mcp[cli]". Затем замените содержимое server.py кодом ниже. В актуальном Python SDK v2 сервер импортируется как MCPServer из mcp.server. Типы int и возвращаемый словарь нужны не только редактору: по ним SDK строит описание инструмента для клиента.
from mcp.server import MCPServer
mcp = MCPServer("Study Progress")
@mcp.tool()
def estimate_progress(solved: int, total: int) -> dict[str, int | str]:
"""Calculate completed practice as a percentage."""
if total <= 0:
raise ValueError("total must be greater than zero")
if solved < 0 or solved > total:
raise ValueError("solved must be between 0 and total")
percent = round(solved / total * 100)
if percent == 100:
status = "готово"
elif percent >= 70:
status = "осталось немного"
else:
status = "продолжайте практику"
return {"percent": percent, "status": status}- Команда
uv run mcp dev server.pyпечатает адрес MCP Inspector. - Во вкладке Tools виден estimate_progress с двумя обязательными целыми полями.
- Вызов с solved=7 и total=10 возвращает percent=70 и статус «осталось немного».
- Вызов с total=0 завершается понятной ошибкой, а не делением на ноль.

У каждого слоя своя работа. Инструмент не заменяет модель, а даёт ей проверяемое действие
Откуда берётся схема инструмента
Когда клиент подключается, сервер объявляет capability tools. Затем клиент запрашивает tools/list и получает имя estimate_progress, текст из docstring и JSON Schema, собранную из аннотаций типов. Если модель решает, что функция поможет ответить, host показывает или выполняет вызов по своим правилам. Запрос tools/call содержит имя и аргументы. SDK превращает значения в параметры Python, запускает функцию и упаковывает словарь в структурированный ответ. Мы не писали JSON-RPC, обработчик маршрута и схему вручную, но все эти слои остаются проверяемыми через Inspector.
| Фрагмент | Что узнаёт клиент | Зачем это модели |
|---|---|---|
| estimate_progress | Стабильное имя инструмента | Можно выбрать нужное действие |
| Docstring | Назначение функции | Понятно, когда её вызывать |
| solved: int | Поле solved должно быть целым | Меньше неверных аргументов |
| dict[str, int | str] | Ответ имеет структуру | Проще использовать результат |
| ValueError | Граница допустимых данных | Ошибка объясняет, что исправить |
Inspector запускает сервер по stdio: протокольные сообщения идут через стандартные потоки, поэтому не печатайте отладочный текст в stdout. Для логов используйте механизм SDK или stderr. Локальный stdio удобен для первого инструмента и не открывает порт наружу. Не передавайте функции больше прав, чем ей нужно. Для чтения заметок ограничьте каталог, для отправки сообщения требуйте явный текст и адресат. MCP стандартизирует соединение, но разрешения остаются вашей задачей.
Проверяем правильные и ошибочные вызовы
Откройте Inspector и сначала вызывайте функцию вручную. Так вы отделите ошибку сервера от поведения модели. Проверьте нормальный прогресс, ноль решённых задач, полное завершение и три неверных набора. Затем переименуйте solved в completed и посмотрите, как изменилась форма. После этого подключите сервер к поддерживаемому MCP-host и сформулируйте запрос без имени функции: «Я решил 7 задач из 10, сколько процентов готово?». В журнале должен появиться именно один вызов estimate_progress.
- Запустите
uv run mcp dev server.pyи откройте напечатанный адрес. - Вызовите инструмент с 0 из 10, 7 из 10 и 10 из 10. Сверьте статус и процент.
- Передайте total=0 и solved=11 при total=10. Запишите тексты обеих ошибок.
- Измените границу статуса с 70 на 80 и убедитесь, что ответ 7 из 10 поменялся.
- Добавьте поле remaining со значением total - solved и проверьте структуру ответа.
- Только после ручной проверки подключите MCP-server к host и посмотрите журнал вызова.
- Inspector видит инструмент и два целых аргумента.
- Правильные значения дают предсказуемый словарь.
- Неверные диапазоны отклоняются внутри Python-функции.
- Вы можете объяснить разницу между host, client, server и tool.

До подключения к реальному host. Сначала ручной вызов, потом решение модели
Расширяем сервер без потери контроля
Следующим шагом добавьте resource с учебным планом или второй tool для выполненной задачи. Не объединяйте чтение и запись в manage_everything. Отдельные инструменты проще описывать и тестировать. Для каждого действия определите минимальный вход, изменение после вызова и видимый результат. Для записи ограничьте тестовый каталог, для API храните ключ в переменной окружения и задайте таймаут. Так пример вырастет в сервер, которому можно доверять.
Как изучить тему в Кодике
В Кодике эту тему лучше проходить не с протокола, а снизу вверх. Сначала закрепите функции, словари, исключения и типы Python. Затем разберите, как программа получает внешние данные. Только после этого возвращайтесь к MCP и подключайте один безопасный инструмент. Такой порядок позволяет видеть в декораторах знакомый код, а не набор непонятных символов.
| Шаг | Что изучить | Мини-проверка |
|---|---|---|
| 1 | Функции и параметры в курсе Python | Написать estimate_progress без MCP |
| 2 | Словари и ValueError | Вернуть структуру и отклонить total=0 |
| 3 | API и границы доступа | Объяснить вход, выход и побочный эффект |
| 4 | MCP tools и Inspector | Увидеть schema и три ручных вызова |
| 5 | Подключение к host | Получить вызов из естественного запроса |
Не измеряйте прогресс числом MCP-серверов. Один инструмент с тестами, понятными правами и обработкой ошибок полезнее пяти копий из видео. В тренажёре Кодика отдельно перепишите функцию расчёта, а затем восстановите декоратор и команду запуска по памяти.
В актуальном v2 берите MCPServer из mcp.server и сверяйте команды с текущей документацией.
Аннотация int не проверяет бизнес-правило. solved всё равно может оказаться больше total.
При stdio они могут смешаться с протокольным обменом. Используйте корректное логирование.
Начните с чтения или расчёта, затем добавляйте подтверждения и узкие разрешения.
Что получится в итоге

Inspector показывает типизированный инструмент, принимает два числа и возвращает точный структурированный результат без ручного протокольного кода.
MCP-сервер сам обращается к языковой модели?
Нет. Он публикует возможности для клиента и выполняет вызовы. Модель находится на стороне host.
Кто решает, когда вызвать tool?
Модель внутри host предлагает вызов, а host применяет свои правила разрешения и подтверждения.
Зачем нужны аннотации типов?
SDK строит по ним схему аргументов, а редактор и тесты раньше замечают ошибки.
Как понять, что проект действительно работает?
Проверьте основной сценарий, ошибочный ввод, повторный запуск и один граничный случай. Затем объясните вслух, где появляются данные, кто принимает решение и где хранится состояние.
Можно ли начать с готового кода из статьи?
Да. После первого запуска измените один параметр, добавьте одну проверку и повторите проект без подсказки. Так пример превращается в собственный навык.
Функции, словари и исключения разберите в курсе Python. Общую картину протокола даёт статья что такое MCP.
После Inspector подключите сервер по инструкции про MCP-host и сравните с практическими сценариями MCP. В Кодике сначала восстановите чистую функцию без декоратора, затем соберите весь файл по памяти.