Зачем конструктору опросов свой MCP-сервер (и почему мы не жалеем)
Привет, Хабр. Меня зовут Дима, я делаю WebAsk — конструктор опросов и тестов. Четыре года назад я писал тут про тотализатор на коленке, спагетти-код из 5к строк и борьбу с мобильным скроллом.
Но сегодня не про это. Сегодня — как мы дали нейронкам прямой доступ к нашему сервису через MCP, какие грабли собрали по дороге и что из этого получилось.
Для тех, кто пропустил хайп: MCP (Model Context Protocol) — открытый стандарт от Anthropic. Он позволяет ллм аля клод, курсор и другим подключаться к внешним сервисам и управлять ими напрямую — без браузера, без ручных оберток, без гемора (ну как сказать). На Хабре про это уже штук пятнадцать статей, пересказывать не буду, сорян))
Зачем нам это сдалось
Мы долго наблюдали один и тот же сценарий. Человек сидит в ллмке — пишет текст, разбирает код, готовит отчет. И тут ему прилетает задача: «надо собрать обратную связь по конференции» или «создай опрос для сотрудников в отделе HR». Что происходит дальше:
Новая вкладка. Логин в сервис. 15 минут тыканья в конструктор. Копирование ссылки. Возврат к ллмке. Контекст потерян, мысль ушла, кофе остыл, коллега уже написал «ну что там с опросом?», а ты еще даже не проснулся
И так каждый раз
Параллельно мы видели, что весь мировой рынок зашевелился. Typeform выкатил свой MCP-сервер в бету. Atlassian, Google, MongoDB — уже подключились. Anthropic и Cursor добавили нативную поддержку MCP-серверов прямо в свои клиенты. Для SaaS это перестало быть экзотикой — MCP превращается в такой же ожидаемый интеграционный слой, как REST API и вебхуки пять лет назад.
У нас к тому моменту был REST API, ИИ-генерация опросов, вебхуки. Добавить MCP-сервер поверх этого было логичным следующим шагом. Не потому что модно, а потому что задача понятная: дать ассистенту прямой доступ ко всему, что умеет наш сервис, чтобы пользователю не приходилось переключаться между окнами.
«Погоди, а зачем конструктор, если ИИ и так умеет в опросы?»
ChatGPT может сгенерить опрос за тридцать секунд. Claude напишет анкету на React с ветвлениями. Зачем тогда отдельный сервис
Потому что сгенерить вопросы — это 5% работы. Остальные 95% — это инфраструктура:
-
Кто захостит опрос? Куда 10 000 респондентов пойдут отвечать?
-
Кто соберет ответы? В какую базу? Кто гарантирует, что ничего не потеряется?
-
Кто посчитает аналитику? NPS-сегментацию из тысячи ответов? Тепловую карту из двух тысяч кликов по макету?
-
Кто экспортирует результаты в Excel для отчета руководителю?
-
Кто отправит данные в Bitrix24, в
MAXTelegram, в Google Sheets?
Это как сказать «зачем ЮКасса, если ИИ может написать платежную страницу». Страницу — может. Платежи обработать — нет. Генерация вопросов — это фронтенд. А за ним нужен бэкенд: хостинг, сбор данных, аналитика с сегментацией, интеграции, экспорт.
MCP как раз про это. Он не помогает ИИ придумывать вопросы — с этим ИИ и сам справляется. Он дает ИИ доступ к работающей инфраструктуре: создать опрос, который реально захостится и будет принимать ответы. Собрать данные с живых респондентов, посчитать NPS, построить тепловую карту по клик-тесту, выгрузить в Excel — и все через один диалог.
ИИ — офигительный интерфейс. Но для проведения исследования ему нужна инфраструктура за спиной. Без MCP он может придумать вопросы. С MCP — провести исследование от создания до экспорта результатов.
Как мы это строили
Ладно, к технике;)
Протокол: JSON-RPC 2.0, авторизация через Bearer-токен. Ключ генерируется в личном кабинете и показывается ровно один раз. Если потеряли — генерируете новый. Так что записывайте сразу, второго шанса не будет.
Подключение к Claude Desktop — стандартный конфиг MCP-сервера:
{
"mcpServers": {
"your-service": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-fetch"],
"env": {
"MCP_URL": "https://your-service.example.com/mcp/v1",
"MCP_AUTH_HEADER": "Authorization",
"MCP_AUTH_VALUE": "Bearer YOUR_API_KEY"
}
}
}
}
Скопировал, вставил, перезапустил Claude Desktop — все, инструменты появились в списке. Для Cursor и VS Code конфиги чуть другие, но принцип тот же
Что внутри. По спеке MCP умеет три штуки: ресурсы (чтение), инструменты (действия) и промпты (подсказки). На деле оказалось, что Claude Desktop поддерживает только инструменты — ресурсы и промпты просто игнорирует. Claude Code и Cursor поддерживают все, но рассчитывать что все сидят именно там — не вариант.
Поэтому мы обернули все ресурсы в инструменты-обертки. Хочешь прочитать ответы? Вызываешь инструмент-обертку. Сводку? Другой инструмент. Структуру опроса? Третий. 19 таких оберток — по одной на каждый ресурс.
В итоге получилось 40+ инструментов действий плюс 19 инструментов чтения — итого под 60 тулзов. Когда мы посчитали итоговое количество — сами немного офигели.
Интересная табличка 👇
|
Группа |
Что внутри |
|
Жизненный цикл |
Создать, переименовать, дублировать, архивировать, удалить, опубликовать |
|
Контент |
Редактировать вопросы, тексты, настройки, логику ветвлений, загружать медиа |
|
Оформление |
Применить тему, создать свою, настроить под бренд |
|
Аналитика |
Сводка, фильтрованные отчеты, теги ответов |
|
Экспорт |
CSV, XLSX, PDF, Word |
|
Промокоды |
Создать группы, добавить коды, управлять списками |
Как это устроено внутри. MCP-сервер написан на Node.js и работает как прослойка между MCP-клиентами (Claude, Cursor) и нашим основным бэкендом. По сути тонкая прослойка: получил JSON-RPC запрос, определил какой инструмент вызвать, провалидировал параметры, дернул API, вернул результат.
Каждый инструмент — отдельный обработчик со своей схемой валидации. Общий роутер разбирает запрос по имени инструмента и перенаправляет в нужный обработчик. Упрощенно это выглядит так:
// описания лежат отдельно от логики
const tools = loadToolDescriptions('./tools/descriptions/')
// роутер
function handleToolCall(name, params) {
const handler = handlers[name] // находим обработчик
const schema = tools[name].params // берем схему валидации
validate(params, schema) // валидируем параметры
return handler.execute(params) // вызываем основной API
}
// обработчик - один файл на один инструмент
// tools/handlers/rename_quiz.js
export function execute({ quiz_id, name }) {
return api.patch(/quizzes/${quiz_id}, { name })
}
Ничего сложного, но когда их под 60 — важно не превратить в кашу:) Один файл = один инструмент, описания отдельно от логики, валидация автоматическая по схеме.
Проблема контекстного окна. Под 60 описаний инструментов — это примерно 6 000 токенов контекста, которые модель получает при каждом обращении. Пробовали отдавать описания группами: сначала только базовые, остальные — по запросу. Не взлетело. Модель не знала о существовании инструментов из непрогруженных групп и даже не пыталась их запросить. Вернулись к полному списку. Дороже по токенам, зато модель видит все и выбирает точнее.
Описания отдельно от кода. Еще одно архитектурное решение, которое себя оправдало: описания инструментов хранятся отдельно от бизнес-логики обработчиков. Итерируем их независимо — можно переписать описание, не трогая код, и наоборот. Звучит очевидно, но когда ты в спешке пишешь все в одном файле — потом это больно разделять.
Окей, это скелет. Дальше интереснее — грабли…(
Его величество описания инструментов
Вот тут мы вообще не ожидали подвоха. На описания инструментов ушло больше времени, чем на саму интеграцию с API. Без шуток
Проблема вот в чем. Когда LLM получает список из 40+ инструментов, она должна по описанию понять, какой из них вызвать и с какими параметрами. Если описание нечеткое или двусмысленное — модель промахивается. Вызывает не тот инструмент. Передает неправильные параметры. Или вообще решает, что нужного инструмента нет, и начинает импровизировать.
Мы сначала пошли по пути экономии. Написали краткие описания — по одному предложению на инструмент. Логика была простая: меньше токенов на контекст — дешевле — быстрее. Красиво на бумаге
На практике: промах в ~30% случаев на сложных цепочках. Модель путала похожие инструменты, не понимала разницу между чтением ответов и чтением сводки. При дублировании опроса забывала передать ID шаблона.
Переписали все описания на подробные — с пояснениями, примерами параметров, типичными сценариями использования. Да, это больше токенов. Да, это немного дороже на каждый запрос. Но точность выросла прям заметно. Экономия на описаниях — ложная экономия.
Конкретный пример: у нас есть два похожих инструмента — один обновляет тексты приветственного и финального экранов, другой — тексты самих вопросов. В первой версии оба были описаны как «обновить тексты опроса». Модель путала их в каждом втором случае. Когда мы явно прописали, что первый работает только с экранами приветствия и благодарности, а для вопросов нужен другой инструмент — путаница прекратилась.
Разница — как между «закрой дверь» и «закрой входную дверь, которая слева, а не дверь в ванную». Для человека и так понятно. Для нейронки — нет
180 запросов в минуту, или Почему агенты — это стихийное бедствие
Отдельная история — лимит запросов. Когда ты делаешь API для людей, 60 запросов в минуту — это с запасом. Человек физически не нажмет кнопку 60 раз за минуту.
С ИИ-агентами все иначе. Агент получает задачу «создай 10 опросов по шаблону с разными параметрами» — и начинает фигачить. Без пауз, без раздумий, без перерыва на кофе. Десять цепочек по 5-7 вызовов = 50-70 запросов за несколько секунд.
60 — безопасно для бэкенда, но агент упрется на любой нетривиальной задаче. 300 — удобно, но один юзер с агрессивным автоматизатором положит сервис остальным. Остановились на 180 — хватает для цепочки из 20+ вызовов, но DDoS от одного агента не устроишь. Ссылки на экспорт живут час
Тестирование, или «Claude, ты что делаешь?»
С юнит-тестами все ок. А потом мы подключили реального Claude и тут началось
Модель — это не детерминированный клиент. Она может вызвать инструменты в рандомном порядке. Додумать параметры. Или просто проигнорить описание и сделать по-своему.
Один из наших любимых багов: мы просили Claude «создай опрос с тремя вопросами и опубликуй». Claude создавал опрос, добавлял три вопроса, потом решал, что неплохо бы еще добавить приветственный экран с мотивирующим текстом (мы не просили), менял тему на «более подходящую» (мы не просили) и только потом публиковал. Инструменты работали корректно. Просто модель решила проявить инициативу :)
Починили жесткими промптами и явными указаниями в описаниях: инструмент делает только то, что написано, и ничего больше. Помогло. Частично
Формальных eval-фреймворков у нас не было. Тестировали руками: ~20 сценариев, прогоняли после каждого изменения описаний. Пишешь промпт, смотришь какие инструменты вызвались, если не те — правишь описание, повторяешь. Описания переписывали по 3-4 раза, некоторые — больше
Больше всего бесили инструменты с похожими названиями и инструменты с кучей необязательных параметров. Тестирование MCP — это, по сути, тестирование промптов. Только промпты — это ваши описания инструментов. Ну вы поняли.
«А где мои ресурсы?», или Сюрприз от Claude Desktop
Это надо знать заранее, серьезно:)
В спецификации MCP три штуки: tools, resources, prompts. Мы сделали ресурсы — для ответов, сводки, структуры опроса, еще полтора десятка. Подключили Claude Desktop. Просим прочитать ответы.
А он не может. Ресурс есть, но Claude Desktop их просто не поддерживает. Только тулзы. Ни ресурсы, ни промпты. Вот так
Claude Code и Cursor — поддерживают все: и ресурсы, и промпты. Но Claude Desktop — самый массовый клиент, и рассчитывать на то, что все пользователи сидят в CLI или Cursor, мы не могли.
Решение: обернули все 19 ресурсов в инструменты-обертки — по одной обертке на каждый ресурс. Тулз вызывает ресурс внутри себя и возвращает данные. Для модели это выглядит как обычный инструмент, для пользователя — разницы ноль.
Мораль: если делаете MCP-сервер и хотите, чтобы он работал не только в CLI — дублируйте ресурсы как тулзы. По спецификации это не нужно. На практике — без этого половина клиентов вас не увидит.
Как этим пользуются на практике
Когда запустились и полезли в логи — оказалось, что мы неправильно понимали вообще все.
Мы думали, что киллер-фича — создание опросов. Ну логично: мы конструктор, значит, люди будут конструировать. Через Claude вместо UI — быстрее, удобнее, контекст не теряется. Пять вызовов — и опрос с вопросами, темой и публичной ссылкой готов. Это и правда работает, и это и правда быстрее
Но самый частый сценарий оказался другим…;)
Главный сюрприз: никто не хотел создавать. Все хотели читать
Аналитику дергают сильно чаще, чем создание. Типичная ситуация: у человека опрос, который уже неделю собирает ответы. Накопилось 500 текстовых ответов на открытые вопросы. И ему нужно из этой каши вытащить смысл — основные темы, повторяющиеся жалобы, неожиданные инсайты.
Раньше это означало: открыть отчет, начать скроллить, к пятидесятому ответу забыть, что было в десятом, к сотому — потерять волю к жизни. На 500 ответах — это полдня работы и желание никогда больше не делать опросы с открытыми вопросами.
Через MCP — Cursor читает все ответы, группирует по темам, выделяет паттерны, считает метрики и отдает выжимку. Плюс экспорт в Excel — и инфа уже готова для отчета руководителю. Минута вместо трех часов:)))
Оказывается, людям лень читать ответы на собственные опросы. Мы их понимаем, тоже такие :)
Но по-настоящему MCP раскрывается в цепочках. Один вызов на создание — это удобно, но не сильно отличается от клика по кнопке. А вот когда агент выстраивает цепочку из 5-9 вызовов подряд — тут начинается совсем другая история.
Пример: автоматизация онбординг-опросов. Каждому новому пользователю через три дня после регистрации нужно отправить персонализированную анкету — с его именем, с вопросами под его тариф. А потом собрать ответы и выгрузить в дашборд. Вручную это делать — хочется уволиться на третьем пользователе. Через MCP агент сам дублирует шаблон, подставляет имя через скрытую переменную, настраивает логику ветвлений под тариф, публикует, а через три дня сам же собирает ответы и экспортирует в CSV. Девять вызовов, ноль ручного труда.
webhook - duplicate - create_hidden_variables - update_texts - update_logic - publish - [3 дня] - answers - export_csv
Скрытые переменные — отдельная штука, которая тут работает на ура. Создаете переменную с именем пользователя, привязываете к полю приветствия — и каждый респондент видит «Привет, Маша!», хотя опрос создан из одного шаблона.
Итерации прямо из IDE. Мы подключили MCP к Cursor, и оказалось, что для UX-ресерчеров это вообще отдельный мир. Прочитал структуру опроса, поправил логику ветвлений, проверил сколько ответов набралось, выгрузил PDF-отчет для стейкхолдеров — и все это не выходя из редактора. Мелочь, но в потоке это экономит не время, а нервы.
Что еще нас удивило
Модель помнит контекст между вызовами — казалось бы очевидно, но на практике прям впечатляет. Человек говорит: «Создай опрос». Потом: «Добавь вопрос про бюджет». Потом: «А покажи, что получилось». Claude помнит айдишник опроса, знает текущую структуру и не теряет нить. Для юзера это выглядит как разговор, а не дерганье API.
Экспорт завершает каждую цепочку. Создал — настроил — собрал — выгрузил в CSV, Excel, PDF или Word. Без экспорта результат остается внутри сервиса. С экспортом — попадает в дашборд, в отчет, в письмо руководителю. Мы чуть не сделали экспорт в последнюю очередь. Это было бы примерно как построить дом и забыть про двери.
MCP vs REST API — зачем два интерфейса
У нас есть полноценный REST API, который покрывает всю функциональность. Зачем тогда MCP?
Потому что потребители разные.
API — для разработчиков. Вы пишете код, точно знаете, какой эндпоинт вызвать, какие параметры передать, как обработать ответ. Все детерминировано, все предсказуемо
MCP — для ИИ-агентов. Вы даете модели набор из ~60 инструментов с описаниями, а она сама решает, что и когда вызвать. Пользователь говорит «создай анкету для оценки сотрудников с NPS-шкалой и логикой ветвлений» — и агент сам разбирает это на шаги, сам выбирает инструменты, сам склеивает результат. Разработчик не писал ни строчки кода
Это не замена. Это параллельный слой. REST API, вебхуки, MCP — три разных интерфейса для трех разных задач. API — для интеграций. Вебхуки — для событий. MCP — для ИИ-агентов.
Короче
Самое неожиданное во всей этой истории — что главная работа оказалась не в коде. Архитектура простая, роутер примитивный, обработчики — обертки над API. А вот тексты описаний инструментов мы переписывали раз пять и до сих пор не уверены, что они идеальные.
Еще мы не ожидали, что люди будут больше читать через MCP, чем создавать. И что Клод в 2026 году все еще не поддерживает ресурсы MCP — пришлось городить обертки
И предвкушаю вопрос “есть ли метрики” — сразу отвечу, что рано, тк запустили недавно. Когда будет что показать — напишу, ну или пните меня)
А если вы делали свой MCP-сервер — расскажите в комментах, как решали проблему описаний. У нас ощущение, что можно лучше, но пока не понимаем как
Респект всем, кто дочитал ❤️
Автор: sheriffdm

