Зачем конструктору опросов свой 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, в MAX Telegram, в Google Sheets?

Это как сказать «зачем ЮКасса, если ИИ может написать платежную страницу». Страницу — может. Платежи обработать — нет. Генерация вопросов — это фронтенд. А за ним нужен бэкенд: хостинг, сбор данных, аналитика с сегментацией, интеграции, экспорт.

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

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

MCP как раз про это. Он не помогает ИИ придумывать вопросы — с этим ИИ и сам справляется. Он дает ИИ доступ к работающей инфраструктуре: создать опрос, который реально захостится и будет принимать ответы. Собрать данные с живых респондентов, посчитать NPS, построить тепловую карту по клик-тесту, выгрузить в Excel — и все через один диалог.

ИИ — офигительный интерфейс. Но для проведения исследования ему нужна инфраструктура за спиной. Без MCP он может придумать вопросы. С MCP — провести исследование от создания до экспорта результатов.

Как мы это строили

Ладно, к технике;)

Общая схема: ИИ-клиент подключается по 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

Промокоды

Создать группы, добавить коды, управлять списками

Claude Desktop после подключения MCP-сервера WebAsk — инструменты появляются в списке автоматически

Под 60 инструментов, разбитых на 6 групп: от создания опроса до управления промокодами

Как это устроено внутри. 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 токенов контекста, которые модель получает при каждом обращении. Пробовали отдавать описания группами: сначала только базовые, остальные — по запросу. Не взлетело. Модель не знала о существовании инструментов из непрогруженных групп и даже не пыталась их запросить. Вернулись к полному списку. Дороже по токенам, зато модель видит все и выбирает точнее.

Описания отдельно от кода. Еще одно архитектурное решение, которое себя оправдало: описания инструментов хранятся отдельно от бизнес-логики обработчиков. Итерируем их независимо — можно переписать описание, не трогая код, и наоборот. Звучит очевидно, но когда ты в спешке пишешь все в одном файле — потом это больно разделять.

MCP-сервер как прослойка: JSON-RPC запрос → роутер → обработчик инструмента → REST API v3 → данные

MCP-сервер как прослойка: JSON-RPC запрос — роутер — обработчик инструмента — REST API v3 — данные

Окей, это скелет. Дальше интереснее — грабли…(

Его величество описания инструментов

Вот тут мы вообще не ожидали подвоха. На описания инструментов ушло больше времени, чем на саму интеграцию с API. Без шуток

Проблема вот в чем. Когда LLM получает список из 40+ инструментов, она должна по описанию понять, какой из них вызвать и с какими параметрами. Если описание нечеткое или двусмысленное — модель промахивается. Вызывает не тот инструмент. Передает неправильные параметры. Или вообще решает, что нужного инструмента нет, и начинает импровизировать.

Мы сначала пошли по пути экономии. Написали краткие описания — по одному предложению на инструмент. Логика была простая: меньше токенов на контекст — дешевле — быстрее. Красиво на бумаге

На практике: промах в ~30% случаев на сложных цепочках. Модель путала похожие инструменты, не понимала разницу между чтением ответов и чтением сводки. При дублировании опроса забывала передать ID шаблона.

Переписали все описания на подробные — с пояснениями, примерами параметров, типичными сценариями использования. Да, это больше токенов. Да, это немного дороже на каждый запрос. Но точность выросла прям заметно. Экономия на описаниях — ложная экономия.

Конкретный пример: у нас есть два похожих инструмента — один обновляет тексты приветственного и финального экранов, другой — тексты самих вопросов. В первой версии оба были описаны как «обновить тексты опроса». Модель путала их в каждом втором случае. Когда мы явно прописали, что первый работает только с экранами приветствия и благодарности, а для вопросов нужен другой инструмент — путаница прекратилась.

Разница — как между «закрой дверь» и «закрой входную дверь, которая слева, а не дверь в ванную». Для человека и так понятно. Для нейронки — нет

Краткие описания это промах в 30% случаев. А подробные - модель выбирает точно. Экономия на описаниях это ложная экономия

Краткие описания это промах в 30% случаев. А подробные — модель выбирает точно. Экономия на описаниях это ложная экономия

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 — и инфа уже готова для отчета руководителю. Минута вместо трех часов:)))

Оказывается, людям лень читать ответы на собственные опросы. Мы их понимаем, тоже такие :)

Cursor читает ответы, группирует по темам и отдаёт структурированную выжимку

Cursor читает ответы, группирует по темам и отдает выжимку

Но по-настоящему MCP раскрывается в цепочках. Один вызов на создание — это удобно, но не сильно отличается от клика по кнопке. А вот когда агент выстраивает цепочку из 5-9 вызовов подряд — тут начинается совсем другая история.

Пример: автоматизация онбординг-опросов. Каждому новому пользователю через три дня после регистрации нужно отправить персонализированную анкету — с его именем, с вопросами под его тариф. А потом собрать ответы и выгрузить в дашборд. Вручную это делать — хочется уволиться на третьем пользователе. Через MCP агент сам дублирует шаблон, подставляет имя через скрытую переменную, настраивает логику ветвлений под тариф, публикует, а через три дня сам же собирает ответы и экспортирует в CSV. Девять вызовов, ноль ручного труда.

webhook - duplicate - create_hidden_variables - update_texts - update_logic - publish - [3 дня] - answers - export_csv

Скрытые переменные — отдельная штука, которая тут работает на ура. Создаете переменную с именем пользователя, привязываете к полю приветствия — и каждый респондент видит «Привет, Маша!», хотя опрос создан из одного шаблона.

От реги юзера до выгрузки ответов в дашборд - 9 шагов, 0 ручного труда

От реги юзера до выгрузки ответов в дашборд — 9 шагов, 0 ручного труда

Итерации прямо из 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 — для ИИ-агентов.

REST API для разработчиков, вебхуки для событий, MCP для ИИ-агентов - три параллельных интерфейса

REST API для разработчиков, вебхуки для событий, MCP для ИИ-агентов — три параллельных интерфейса

Короче

Самое неожиданное во всей этой истории — что главная работа оказалась не в коде. Архитектура простая, роутер примитивный, обработчики — обертки над API. А вот тексты описаний инструментов мы переписывали раз пять и до сих пор не уверены, что они идеальные.

Еще мы не ожидали, что люди будут больше читать через MCP, чем создавать. И что Клод в 2026 году все еще не поддерживает ресурсы MCP — пришлось городить обертки

И предвкушаю вопрос “есть ли метрики” — сразу отвечу, что рано, тк запустили недавно. Когда будет что показать — напишу, ну или пните меня)

А если вы делали свой MCP-сервер — расскажите в комментах, как решали проблему описаний. У нас ощущение, что можно лучше, но пока не понимаем как

Респект всем, кто дочитал ❤️

Автор: sheriffdm

Источник

Оставить комментарий