Представьте диалог c вашим любимым ИИ‑инструментом:

— Найди поездку в Казань на выходные, до 20 тысяч рублей и с нормальным отелем.

— Секунду, подбираю…

«Подбираю» в этом диалоге — это некоторый чёрный ящик. Под ним могут скрываться воспоминания модели из обучения, попытки воспользоваться веб‑браузером, поиском в интернете. Такие обращения могут содержать различного рода погрешности.

А вот чтобы это «подбираю» означало поиск по реальным ценам и наличию агент должен уметь обращаться к базе данных с билетами. Именно для этого мы вместе с командой Туту выпустили MCP‑сервер.

По оценке McKinsey, к 2030 году ИИ‑агенты могут участвовать в продажах товаров на сумму от 3 до 5 трлн долларов по всему миру.

MCP (Model Context Protocol — протокол контекста модели) — открытый протокол, через который ИИ‑агент узнаёт о доступных инструментах сервиса и вызывает их сам, а не разбирает страницы сайта.

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

Меня зовут Александр Поляков, я проектирую ИИ‑продукты. Этот MCP‑сервер мы собрали вместе с командой Туту, поэтому все семь наблюдений ниже — из практики.

У MCP‑сервера должен быть лендинг

Первый отзыв пришёл не от ИИ‑инженеров, а от обычных пользователей. Они присылали скриншоты белого экрана с сообщением:

{"detail": "Method Not Allowed"}

И это логично. Человек первым делом нажимает на то, что выглядит как ссылка. Если адрес MCP‑сервера при обычном переходе из браузера показывает техническую ошибку, кажется, что всё сломалось. Разбираться дальше никто не станет.

Решение простое: если сервер умеет отличить агента от браузера, браузеру стоит показывать страницу с инструкцией. Мы различаем запросы по методу и заголовку Accept:

Что пришло в запросе

Что отдаём

POST

Ответ агенту в формате JSON‑RPC (JavaScript Object Notation Remote Procedure Call — формат удалённых вызовов)

GET с Accept: text/event‑stream

Поток событий агенту через SSE (Server‑Sent Events — события от сервера)

GET с Accept: text/html

Лендинг, или посадочную страницу

Лендинг для пользователей поможет объяснить, что делать дальше
Лендинг для пользователей поможет объяснить, что делать дальше

Лендинг объясняет человеку, что это за сервер, как его подключить и для каких сценариев использовать.

Здесь есть тонкость. Некоторые агенты приходят с неопределённым заголовком Accept, поэтому их нельзя случайно отправить на страницу для человека. Безопасное правило по умолчанию — отвечать как агенту, а лендинг показывать, только если мы точно распознали браузер. Боты, которые создают предпросмотр ссылок в мессенджерах, тоже получат страницу — в этом случае так и задумано.

Итог. Человек видит описание сервера и способы подключения, а агент — доступные методы и инструкции для работы.

Инструменты должны объяснять себя сами

MCP позволяет передавать агенту общие инструкции, но длинный гайд быстро расходует контекст. Надёжнее, когда каждый инструмент сам сообщает, что умеет, какие параметры принимает и как с ним обращаться.

Например, описание поиска отелей может выглядеть так:

{
   "name": "search_hotels",
   "description": "Поиск отелей в городе на даты заезда и выезда. Возвращает отели с ценой, рейтингом и идентификатором для оформления.",
   "inputSchema": {
 	"type": "object",
     "required": ["city", "checkin", "checkout"],
 	"properties": {
   	"city": {
     	"type": "string",
     	"description": "Город или его идентификатор"
   	},
   	"checkin": {
     	"type": "string",
     	"format": "date"
   	},
  	 "checkout": {
     	"type": "string",
     	"format": "date"
   	},
   	"guests": {
     	"type": "integer",
     	"default": 2
   	}
 	}
   },
   "annotations": {
 	"readOnlyHint": true,
 	"idempotentHint": true,
 	"openWorldHint": true
   }
 }

Здесь агенту уже сообщено всё необходимое:

  • readOnlyHint — инструмент только читает данные и ничего не меняет;

  • idempotentHint — повторный вызов безопасен;

  • openWorldHint — инструмент обращается во внешний сервис.

Дублировать эту информацию в общем гайде не стоит. Иначе появятся два источника инструкций, которые рано или поздно разойдутся, а поведение агента станет непредсказуемым.

Подробные руководства для сложных предметных областей мы тоже не держим в каждом сеансе. Их вынесли в отдельные методы вида get_<домен>_instructions, которые агент вызывает только при необходимости.

Итог. Хорошее описание и аннотации делают инструмент понятным без дополнительных инструкций.

Агрессивно экономим контекст: минус 74%

Первую версию сервера мы тестировали на GPT-5.4 — всё работало стабильно. Когда запустили те же сценарии на Qwen3-30B‑A3B с контекстным окном примерно в 130 тысяч токенов, начались проблемы.

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

Можно было добавить инструкцию: сначала сохранять ответ на диск, а потом искать по нему с помощью grep. Или расширить MCP с помощью отдельного навыка. Но оба варианта усложняют настройку и остаются ненадёжными.

Поэтому мы добавили постраничную выдачу даже там, где раньше она не требовалась. Например, карта мест в поезде из девяти вагонов занимала около 140 КБ. Мы стали подробно показывать первые вагоны, а остальные заменили краткими блоками с пометкой «продолжение по запросу». В карточке отеля могло быть от 20 до 35 ссылок на фотографии — оставили обложку и общее количество снимков.

На типовых ответах получили такие результаты:

Ответ инструмента

Было

Стало

Экономия

Детали по поезду

66–75 КБ

14–17 КБ

75%

Карточка отеля

58 КБ

16 КБ

72%

Сценарий целиком в памяти

около 133 тыс. токенов

около 34 тыс. токенов

74%

Главная оптимизация проекта: на типовом сценарии объём занятого контекста сократился на 74%.

Отсюда практический приём: проверяйте MCP на небольших моделях с коротким контекстным окном. Они быстрее проявляют пограничные сценарии, которые незаметны при оценке на более крупных моделях.

Итог. После оптимизации даже локальная Qwen3-30B‑A3B в OpenCode проходит все наши тесты при небольшом контекстном окне.

Разработка MCP‑сервера — ещё и юридическая задача

Когда мы дошли до оплаты, на архитектуру повлияли два требования:

  • пользователь должен явно и осознанно принять оферту;

  • если передавать персональные данные или билеты в интерфейс иностранного сервиса, может возникнуть трансграничная передача данных. Для неё закон предусматривает отдельную процедуру, включая предварительное уведомление Роскомнадзора.

Поэтому последний шаг покупки всегда проходит в сеансе на сайте продавца. Сейчас через MCP агент может подобрать поездку и довести пользователя до этапа, на котором нужно ввести персональные данные и оплатить заказ. Оферту принимает, данные вводит, билет оплачивает и скачивает уже сам пользователь.

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

Итог. Агент помогает выбрать и подготовить заказ, но юридически значимые и чувствительные действия остаются под контролем пользователя и продавца.

Авторизация: поддерживаем оба способа

Если начать разбираться с авторизацией агентов через MCP, легко запутаться в противоречивых рекомендациях. Мне понравилось, как коллеги из Битрикс24 объяснили выбор токенов. Был и собственный опыт: MCP Granola удобно подключается к приложениям ChatGPT и Claude через OAuth, а с агентом Hermes пришлось повозиться — отдельно запрашивать ссылку и передавать ему redirect_url.

В стандарте MCP авторизация для удалённых HTTP‑серверов (Hypertext Transfer Protocol — протокол передачи гипертекста) описана через OAuth 2.1 — протокол, который позволяет приложению получить ограниченный доступ без передачи пользовательского пароля. Поэтому облачные клиенты, включая ChatGPT и Claude, ожидают именно такой сценарий: вставить в них произвольный ключ обычно нельзя.

Локальные клиенты работают иначе. Они могут хранить секрет на устройстве пользователя и передавать его в заголовке запроса, поэтому полноценный OAuth для них бывает избыточен. Для таких клиентов мы поддерживаем статический ключ в заголовке как дополнительный способ авторизации.

Клиенты пока не одновременно внедряют новые возможности стандарта, поэтому на переходном этапе приходится поддерживать оба варианта.

Чего точно нельзя делать — передавать ключ в адресе, например:

https://site.ru/mcp?key=xxxxxx

Спецификация MCP прямо запрещает помещать токен в строку запроса: он может попасть в логи и историю браузера.

Любой способ авторизации также требует раздела в личном кабинете. Пользователь должен видеть всех подключённых агентов, уметь отозвать любой токен, ограничить расходы агента и связать доступ с принятием оферты.

Итог. Поддерживаем OAuth 2.1 и ключ в заголовке, показываем подключённых агентов в личном кабинете и никогда не передаём секрет в URL (Uniform Resource Locator — адрес ресурса).

Тестируем не только юнит‑тестами, но и эвалами

Юнит‑тест проверит, что инструмент вернул ответ нужной формы. Но он не заметит главного сбоя в поведении агента: например, если тот не вызвал нужный инструмент или после изменения сигнатуры вызова стал хуже справляться со сценарием.

Поэтому кроме юнит‑тестов мы используем эвалы — сценарные проверки качества. Запускаем живого агента на реальных запросах, а вторая модель оценивает его действия по заданным критериям.

Важно различать источник ошибки:

  • агент получил нужные данные, но неправильно ими распорядился;

  • сервер не вернул данные, без которых задачу нельзя решить.

Всего у нас есть более 60 сценариев, которые позволяют оценить качество работы агента.
Всего у нас есть более 60 сценариев, которые позволяют оценить качество работы агента.

Сейчас у нас больше 60 сценариев, по которым мы оцениваем качество работы агента. Кроме того, мы собрали небольшой бенчмарк — набор одинаковых испытаний, который показывает, какие модели лучше справляются с конкретными задачами через наш MCP.

Пришлось сохранить результаты API и запускать MCP с моковым флагом: если агент делает правильный запрос, получает ответ, если неправильный — мусор в данных и не проходит тест.
Пришлось сохранить результаты API и запускать MCP с моковым флагом: если агент делает правильный запрос, получает ответ, если неправильный — мусор в данных и не проходит тест.

Чтобы результаты были воспроизводимыми, мы сохранили ответы API (Application Programming Interface — программный интерфейс приложения) и запускаем MCP в режиме моков, то есть с заранее подготовленными данными. Если агент формирует правильный запрос, он получает корректный ответ. Если ошибается, сценарий не проходит.

Во время разработки мы закрепили правило: для каждой новой возможности сразу описываем набор тестовых сценариев, а после крупных обновлений запускаем полный прогон. Так мы регулярно находим неожиданные эффекты и ошибки.

Итог. MCP поддерживает гибкий набор пользовательских сценариев, поэтому и тестировать его нужно сценариями — так можно заметить поломки именно в поведении агента.

Ограничиваем частоту запросов и объясняем правила

Ограничение частоты запросов, или рейт‑лимит, по привычке хочется включить для всего сервера. Но у нас на одном адресе находятся и лендинг, и инструменты, поэтому лимит действует только на вызовы инструментов.

Если сервер отвечает кодом 429 Too Many Requests — “слишком много запросов”, — агенту стоит сразу сообщить, когда лимит сбросится, сколько запросов разрешено и как лучше перестроить работу.

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

Поэтому мы привязали лимит к вызовам API Туту, а не ко всему серверу.

Итог. Ограничиваем агрессивный сбор результатов поиска, но не доступ к лендингу и описаниям инструментов.

Что в итоге

MCP отличается от привычного REST API (Representational State Transfer — архитектурный подход к программным интерфейсам) не только способом взаимодействия. Такой сервер приходится объяснять сразу двум аудиториям: людям, которые подключают агента и получают результат, и самим агентам, которым нужно правильно выбрать и вызвать инструмент.

На практике качество MCP складывается не только из корректного кода. Нужны понятный лендинг, самодостаточные описания инструментов, компактные ответы, продуманная авторизация, юридически безопасный путь к покупке, сценарные тесты и прозрачные ограничения.

Делитесь в комментариях своими приёмами: как вы проектируете MCP и на какие грабли уже наступили? Особенно интересно, если ваш агент уверенно придумывал то, чего сервис никогда не возвращал.


P. S. Пишу про ИИ, код и кейсы агентной коммерции в телеграм‑канале «Поляков считает».

Комментарии (2)