За вечер соберёте на Python MCP-сервер: программу, через которую Claude Desktop, Cursor или ваш агент вызывают ваши функции и читают ваши данные. В финале у вас будет сервер базы знаний, который не отдаёт закрытые документы. Ломается обычно на мелочах. Сервер работает в терминале, но в приложении его нет. Лишний print в stdout вешает клиента, а ошибка, возвращённая строкой, уходит модели как ответ. Ставить ничего не нужно, ключи API тоже: лабы идут в песочнице на официальном Python SDK.
В финале курса вы собираете MCP-сервер базы знаний учебной компании «Ортус». Модели внутри сервера нет: какой инструмент вызвать, решает модель в приложении, которое подключило сервер. В базе вперемешку лежат тарифы и документация API, зарплатные вилки и условия сделок. У каждого документа свой access_level: public, internal или restricted.
Просьба к модели «не показывай закрытое» защитой не считается. Граница стоит в коде сервера: search строит BM25 только по публичным документам, а get_document на закрытый и на несуществующий id отвечает одной и той же ошибкой.
Автопроверка лабы, настоящий MCP-клиент, ищет зарплатные вилки по грейдам, бонусный пул руководителей и аварийный доступ Vault break-glass. Ни одного непубличного документа в выдаче быть не должно. Prompt answer_with_sources требует ставить источник [doc_id] после каждого утверждения.
Python-разработчикам, которые подключают свои данные и API к Claude Desktop, Cursor или собственному агенту.
Тем, у кого сервер работает в терминале, но в приложении не появляется или клиент на нём зависает. Здесь причину ищут по сообщениям протокола.
Инженерам, которым мало декораторов SDK: нужно видеть, какая строка ушла в stdin сервера и какая пришла из stdout.
Инженерам, которые открывают агенту внутреннюю базу документов и отвечают за то, что он оттуда достанет.
Python: функции, словари, исключения, запуск процесса через subprocess.
Умение читать асинхронный код: async with, await, asyncio.run. Клиент MCP в курсе асинхронный.
pydantic BaseModel и Field пригодятся: на них строится структурированный ответ инструмента.
JSON: объекты, массивы, строки.
Код в курсе на Python. Если вы пишете на Go, протокол и ошибки те же, но лабы сдаются на Python.
Первым ломается рукопожатие. Клиент шлёт initialize, сервер отвечает serverInfo и capabilities, клиент отправляет notifications/initialized. Без последней строки сервер отклоняет tools/list. Это самая частая ошибка, когда работаете с протоколом руками: клиент ждёт result и падает с KeyError.
Поэтому первым вы пишете клиента без SDK: subprocess и строки JSON-RPC. Каждое сообщение пишется одной строкой JSON с \n в конце, после записи нужен flush. Без flush клиент висит на readline, а лишний print в stdout сервера ломает поток. Между запросом и ответом может прийти notifications/message, свой ответ находят по id.
Дальше эти строки спрячет SDK, но при сбое вы будете знать, что ушло в сервер и что вернулось.
Модель передаёт инструменту что угодно: limit=0, строку вместо числа, имя заметки, которой нет. Сервер должен ответить так, чтобы отказ было видно. Ошибка инструмента приходит с isError: true, ошибка ресурса или prompt приходит как McpError, а поиск без совпадений отдаёт пустой список без ошибки.
Опаснее всего безобидная на вид строка return "Ошибка: деление на ноль". Для протокола это успешный ответ с isError false, и модель примет его за результат. Автопроверка такую работу не засчитает: ошибку нужно бросить исключением.
С типами то же самое. Значение, которое к типу не приводится, например "много" вместо числа, FastMCP отсекает сам: функция не вызывается, клиент получает isError. А "3" для int он молча превратит в 3, так что на схему целиком не рассчитывайте. Без аннотаций параметр попадает в схему как string, клиент шлёт "2", и сложение превращается в склейку строк. Поэтому автопроверка вызывает ваш add с отрицательными и случайными числами, а search с limit вне диапазона от 1 до 10.
Зелёный тест ещё ничего не доказывает. Сломанный add, который складывает модули чисел, пройдёт add(2, 3) и упадёт только на add(-2, 5).
Вы пишете контрактный тест: путь к серверу передаётся аргументом, итог отдаётся кодом выхода 0 или 1, при нарушении одна строка FAIL. Автопроверка запускает его на пяти серверах. Два правильных, в трёх есть поломки: divide возвращает строку вместо isError, add теряет знак, ресурс notes:// отдаёт не тот текст.
Пропустите поломку и увидите: «тест не поймал сломанный сервер… Тест завершился кодом 0, а должен 1». Клиента и конфиг автопроверка тоже запускает по-настоящему. Клиент обязан смотреть result.isError до чтения content. В mcpServers команда python должна быть абсолютным путём: приложение запускается не из вашего терминала и не видит ваш PATH.
Продолжить тему на Mentorix можно в курсе «MCP Integrations». Там серверы работают с PostgreSQL и pgvector, опасные действия идут через подтверждение, доступ закрывается токенами со scopes, а агента защищают от prompt injection.
Вопросы по курсу можно задать поддержке: t.me/mentorix_support.
Модулей: 3, уроков: 12. Задачи с кодом проверяются автоматически.
Бесплатный курс