Как я делал свой первый MCP-сервер для RetroPoint
В браузере у меня были две регулярные встречи с накопленной историей. AI-агент через новый MCP-сервер получал пустой список. Запрос проходил, ошибок не было, тесты были зелеными. Просто встреч как будто не существовало.
Так выглядела одна из первых проверок моего MCP для RetroPoint. Я делаю этот сервис для ретроспектив, рабочих встреч и договоренностей команды. Хотел дать внешнему AI-агенту возможность собирать контекст и записывать результат обратно в продукт. По дороге пришлось разобраться, что именно я считаю работающей интеграцией.
Зачем мне понадобился свой MCP
При подготовке к встрече факты приходится собирать из нескольких мест: задачи в трекере, изменения в репозитории, договоренности прошлого раза. Потом все это нужно превратить в повестку и перенести в сервис встреч.
Если AI-агент уже умеет читать трекер и Git, хочется поручить ему и следующий шаг. Пусть найдет нужную встречу в RetroPoint, посмотрит прошлый протокол и поможет заполнить новый. Человек проверит содержание, а переносить один и тот же текст между окнами не придется.
Для этого и нужен MCP, Model Context Protocol. Через него приложение сообщает AI-клиенту, какие инструменты доступны, какие параметры им передавать и какой результат ожидать. Например, найти встречу, прочитать ее повестку или добавить согласованный пункт. Модель выбирает вызов по задаче пользователя, а сервер проверяет запрос и выполняет разрешенную операцию. Так устроены инструменты в спецификации MCP.
Сам MCP не дает агенту доступ к трекеру, Git и всем остальным источникам разом. Их нужно подключить отдельно. Сервер RetroPoint отвечает за свою часть: доступные пользователю доски, встречи, действия и другие рабочие данные.
В продукте уже был встроенный AI-помощник. У MCP другая роль: человек приходит со своим агентом, своей моделью и контекстом из других систем. Поэтому подключение я сделал бесплатным на всех тарифах. Оно не расходует квоту встроенного AI, но сохраняет ограничения модулей и прав пользователя. Внешний AI-клиент оплачивается по правилам его поставщика.
Сначала я определил, что агенту можно менять
Одного чтения для моего сценария было мало. Если агент подготовил повестку, а я все равно должен скопировать ее в форму, часть ручной работы остается. Поэтому первая версия сразу включала запись.
В ТЗ я зафиксировал несколько границ. Агент работает от имени пользователя и не получает дополнительных полномочий из-за того, что подключился через MCP. Разрешение токена сужает доступ, но не повышает роль. Приватные заметки встреч один на один и Performance Review внешнему агенту не передаются.
Запрет нельзя оставлять только в инструкции модели. Данные, которые агенту не положено видеть, вообще не должны попадать в ответ сервера.
Для изменений понадобились отдельные правила. Заполнить свое пустое поле протокола и заменить уже написанный текст имеют разные последствия. Во втором случае сервер запрашивает подтверждение конкретной операции. Если человек успел изменить данные после того, как агент их прочитал, проверка версии помогает остановить запись поверх новых правок.
Существующую логику продукта я использовал повторно. MCP-инструменты вызывают те же сервисы, которые обслуживают веб-интерфейс. Отдельную модель прав для агентов я не создавал. Но, как выяснилось на встречах, выбрать правильный сервис тоже нужно уметь.
Первый запуск и пропавшие встречи
ТЗ появилось 26 августа. 2 сентября я включил MCP для двух учетных записей, а 3 сентября выпустил публично.
Внутри существующего backend на FastAPI я сделал собственный небольшой адаптер протокола. Он принимал запросы клиента, находил нужный инструмент и передавал выполнение в сервис продукта. Такой вариант позволил обойтись без отдельного приложения, но корректность ответов и совместимость с клиентами тоже остались на мне.
На первых рабочих проверках нашлось восемь дефектов. Самый наглядный был с тем самым пустым списком встреч.
В RetroPoint к этому моменту существовали два варианта встреч. Одни были привязаны к команде, другие можно было создавать независимо от ее состава. MCP обращался только к сервису командных встреч. Тот начинал проверку с загрузки команды, которой у второго варианта просто не было.
Тестовые данные покрывали встречи внутри команд. Мои реальные встречи жили по другому сценарию. Поэтому отдельный инструмент успешно проходил проверки и не решал мою задачу.
Я добавил работу через сервис второго вида встреч с его собственными правилами доступа. После этого агент увидел нужную историю. Этот случай хорошо показал, зачем проверять интеграцию на данных, которые продукт накопил за время развития: они не обязаны выглядеть как свежие тестовые примеры.
Исправление, которое ничего не исправило
Другая находка касалась повторных запросов. Агент может повторить команду, например если не получил ответ. Для таких случаев используется ключ идемпотентности: по нему сервер узнает уже выполненную операцию и возвращает сохраненный результат.
Мне нужно было различать в журнале создание объекта и возврат прежнего результата. Я добавил отдельную отметку повтора, необходимые поля и обработку. Но функцию, которая ставила эту отметку, не вызвал ни в одной из шести ветвей повторного выполнения.
Тесты проходили. Они проверяли признак повтора в ответе инструмента, а он формировался в другом месте и работал все это время. Исправление относилось к журналу, проверка смотрела на ответ.
После этого я добавил проверку самой записи в журнале: нужный исход и ссылку на тот же объект. Для себя забрал простой вопрос к любому тесту: он наблюдает именно то изменение, которое я собираюсь выпустить?
У повторов позже нашелся еще один нюанс. Последовательный повтор с готовым результатом и два одновременных запроса с одним ключом требуют разной обработки. В сентябре обнаружилась гонка: оба запроса успевали выполнить изменение до сохранения результата. Поэтому наличие ключа в API само по себе еще не означает, что любой дубль исключен.
Пустое поле оказалось непустым
После запуска проблемы продолжили приходить из обычных действий пользователя. Например, человек очищал поле протокола в редакторе, а агент не мог просто заполнить его заново: сервер требовал подтверждения замены текста.
Визуально поле было пустым. Внутри редактор оставлял HTML вроде <p></p>. Моя проверка смотрела на строку с разметкой и находила в ней содержимое.
Исправление было небольшим: сначала получить текст из HTML, затем проверить, осталось ли что-то содержательное. Пустая разметка перестала считаться написанным протоколом. Защита настоящего текста сохранилась.
Рядом обнаружилась более интересная проблема. Когда сервер действительно просил подтверждение, код для повторного вызова лежал в структурированной части ответа. Некоторые клиенты передавали модели только текстовую часть. Агент получал просьбу повторить операцию с кодом, но самого кода не видел.
Сервер ответил, клиент получил ответ, а продолжить сценарий было невозможно. Детали подтверждения пришлось добавить и в текст, который доходил до модели.
Один клиент работал, другой отвергал тот же ответ
В начале октября проблема с подтверждениями проявилась еще раз при работе с агентом на базе Qwen Code.
Я описал схему успешного ответа инструмента. Но отказ или запрос подтверждения имел другую структуру. Этот клиент проверял ее по той же схеме и отбрасывал ответ еще до передачи модели. В проверенном сценарии Claude его принимал, поэтому работающее подключение в одном клиенте скрывало ошибку контракта.
Пришлось описать оба результата: успешное выполнение и предметную ошибку. Это исправление сделал в общем реестре инструментов, чтобы одинаковая проблема не оставалась в соседних операциях.
Затем всплыл запуск клиента. Сервер объявлял только инструменты, а агент на базе Qwen Code дополнительно запрашивал списки других возможностей MCP. Ответ «метод не поддерживается» с HTTP-ошибкой клиент воспринимал как сбой подключения. Появлялось сообщение о неудачном старте, хотя отдельные инструменты отвечали.
Для этих списков я добавил пустые ответы и скорректировал обработку ошибок протокола. Проверять только успешный вызов оказалось недостаточно. В рабочем сценарии есть еще подключение, отказ, подтверждение и повтор после отказа. Каждый из этих шагов должен быть понятен конкретному клиенту.
Во что выросла первая версия
Первое подключение требовало вручную выпустить токен и вставить его в настройки AI-клиента. 26 сентября я добавил OAuth: пользователь указывает адрес MCP, входит в RetroPoint и выбирает разрешения. Подключение можно отдельно отозвать в профиле. Личные токены тоже остались.
К этому же выпуску агент научился готовить и вести ретроспективу: создавать доску по шаблону, работать с карточками и темами, читать доску целиком, запускать таймер. Для удаления, объединения, голосования и архива нужны отдельные права и подтверждения. Голосовать можно только от имени подключенного пользователя.
В октябре появились создание разовых и регулярных встреч, пакетная запись повестки и выбор способа отправки приглашений. Здесь возникла уже следующая практическая задача: если календарное приглашение отправляет другой сервис, RetroPoint должен уметь сохранить встречу без второго письма участникам.
Хороший пример сценария для такой интеграции: найти ближайшую встречу, прочитать прошлые договоренности, сопоставить их с доступными агенту задачами и предложить новую повестку. После проверки человеком сохранить согласованное в нужную встречу. В этом сценарии ценен весь путь, включая возврат результата в продукт.
Подключение и доступные разрешения описаны на странице MCP RetroPoint.
Что я теперь проверяю в MCP
С этим опытом я бы начинал проверку с одной законченной задачи пользователя. Например, подготовить повестку конкретной встречи на реальных данных. По пути смотреть, нашел ли агент нужный объект, получил ли достаточно контекста, понял ли отказ и смог ли сохранить результат после подтверждения.
Отдельно проверял бы несколько неприятных случаев: поле очистили в редакторе, человек успел изменить текст, команда пришла повторно, клиент строго проверяет схему ответа. Именно такие детали у меня ломали сценарий при работающем сервере.
Первый релиз дал возможность подключить агента к RetroPoint. Последующие исправления помогли довести конкретные действия до результата. Следующий вопрос уже про пользу: какие из этих сценариев люди будут повторять в обычной работе. По числу инструментов и зеленым тестам этого не узнать.