Skip to content
Home Prices Guides FAQ Journal

MCP-сервер для eSIM — руководство разработчика

Как мы построили первый агентно-ориентированный API для eSIM: жизненный цикл токена, пятиуровневая защита трат, HMAC-проверка платежей, аварийные «рубильники» и проектные решения, благодаря которым криптовалютные рельсы уникально подходят для автономных агентов.

Большинство онлайн-сервисов создавались для браузеров. Пользователь сидит перед страницей с авторизацией по session-cookie, нажимает кнопки, заполняет поля, и бэкенд исходит из этого сценария на всём пути. AI-агенты в эту форму не вписываются — у них нет браузерной сессии, нет DOM, по которому можно кликать, нет камеры, чтобы отсканировать QR-код, и нет терпения на танцы с OAuth. Создавать продукт для агентов — значит перестраивать слои авторизации, оплаты и доставки вокруг иных предположений.

Этот материал разбирает архитектурные решения, лежащие в основе MCP-сервера Roamzy. Он предназначен для разработчиков, создающих AI-агентов, которые взаимодействуют с реальными коммерческими системами, а также для инженеров других сервисов, которым интересно, как «готовый для агентов» API на самом деле выглядит в продакшене.

MCP-сервер — это тонкая обёртка

Сам MCP-сервер невелик — около 400 строк TypeScript, собранных в архив на 100 КБ с помощью esbuild. Он существует лишь для того, чтобы переводить между двумя протоколами: MCP-запросами на основе stdio от Claude Desktop / Cursor / Continue и HTTPS REST-вызовами к https://roamzy.io/api/v1/*.

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

Это правильная граница. Размещение бизнес-логики в MCP-сервере разнесло бы её по N+1 местам (сервер + установленный архив у каждого клиента), создало бы отставание по обновлениям и расширило бы поверхность атаки на ноутбуке пользователя. Сохраняя MCP-сервер как чистый «клей» для HTTP-клиента, мы получаем тонкий слой, который легко проверить (около 30 минут для пользователя, заботящегося о безопасности) и обновить, просто переопубликовав архив по тому же URL.

Токены хешируются по SHA-256 и показываются один раз

Формат токена — rk_live_<32-char-base64url>, в стиле Stripe. Префикс фиксирован, чтобы GitHub Secret Scanning и подобные инструменты могли обнаруживать утечки. Тело — 24 случайных байта в кодировке base64url, что даёт 192 бита энтропии.

При создании открытый текст показывается пользователю ровно один раз, в жёлтом блоке с предупреждением. Сервер хранит только SHA-256-хеш. 12-символьная подсказка-префикс (rk_live_abc1) сохраняется для отображения, чтобы пользователь мог различать токены, когда активно сразу несколько.

Для сравнения используется timingSafeEqual из Node, чтобы исключить тайминговые атаки. Отзыв токена реализован как «мягкое удаление» (установка revoked_at), чтобы сохранялся аудит-трейл. Отзыв вступает в силу немедленно: следующий API-вызов с отозванным токеном возвращает 401 за миллисекунды.

Пятиуровневая защита трат

В большинстве систем на основе токенов один или два лимита трат. У нас их пять, и каждый закрывает свой сценарий отказа:

  1. Дневной лимит на токен (по умолчанию $50 USDT, настраивается в пределах $1–$1000). Ограничение на траты за скользящие 24 часа.
  2. Месячный лимит на токен (по умолчанию $500, настраивается в пределах $1–$10000). Ограничение на траты за скользящие 30 дней.
  3. Период остывания — первые 7 дней после создания токена суммарные траты ограничены $50 USDT независимо от дневного лимита. Зашит в код; повысить нельзя. Это ограничивает радиус поражения, если токен утечёт сразу после создания (разработчик по ошибке вставил его в публичный gist).
  4. Порог крупной транзакции (по умолчанию $200) — покупки выше этой суммы требуют ручного подтверждения человеком-пользователем в личном кабинете. API возвращает big_txn_needs_confirmation, и агент обязан показать это пользователю.
  5. Отдельное включение права на покупки — по умолчанию при создании токена флажок «Allow purchases» снят, то есть токен может вызывать только эндпоинты чтения. Пользователь должен сознательно его включить. Для агента, которому нужно лишь запрашивать каталог, это самая безопасная конфигурация.

Счётчики живут в таблице api_tokens с ленивой ротацией окна: когда запрос читает счётчик, если сохранённое окно (YYYY-MM-DD для дня, YYYY-MM для месяца) не совпадает с текущим, счётчик считается нулевым. Это позволяет обойтись без ежедневного cron-задания и устойчиво к расхождению машинных часов.

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

Целостность платежей — HMAC и уникальные ограничения

Самый важный уровень защиты от мошенничества — проверка HMAC-SHA-512 на вебхуке NowPayments. Общий секрет (IPN_SECRET) хранится в окружении сервера. Каждый payload вебхука приходит с заголовком x-nowpayments-sig; сервер вычисляет HMAC по «сырому» телу запроса и сравнивает. Без секрета злоумышленник не сможет подделать payload со статусом «оплачено» — даже если он знает ID заказа и ID intent (а он, скорее всего, знает, поскольку они возвращаются в ответе на заказ).

Второй уровень — UNIQUE-ограничение на уровне БД для пары (ref_type, ref_id) в таблице ledger. Каждое зачисление записывается с ref_type="nowpayments" и ref_id=<payment_id>. Если вебхук сработает дважды для одного payment_id (повтор из-за сети, replay-атака), вторая вставка нарушит уникальное ограничение, и приложение коротко завершится с alreadyApplied=true. Двойное зачисление невозможно.

Третий уровень — архитектурный: только функции из services/billing.ts могут изменять баланс eSIM. Нет админского эндпоинта для произвольного добавления баланса; даже админский инструмент «активировать eSIM вручную» проходит через тот же путь зачисления. Это минимизирует поверхность атаки — существует ровно один путь в коде, по которому баланс может вырасти, и это путь со всеми проверками безопасности.

Три аварийных «рубильника» для реагирования на инциденты

Что-то обязательно пойдёт не так. Токены утекают. Агент пользователя ломается и спамит заказами. У платёжного провайдера случается сбой. Мы сделали три независимых «рубильника», чтобы реакция была соразмерной:

  • Отзыв отдельного токена — пользователь нажимает Revoke в личном кабинете. Затрагивает только этот токен. Применяется, когда один агент ведёт себя неправильно или токен утёк.
  • Блокировка агентов для пользователя — админ вызывает POST /api/admin/users/:id/agent-block с указанием причины. Затрагивает все токены этого пользователя. Применяется при злоупотреблении или компрометации отдельного аккаунта.
  • Глобальная остановка агентов — админ вызывает POST /api/admin/agents/pause. Каждый Bearer-токен у всех пользователей начинает возвращать 503. Применяется при инфраструктурных инцидентах или расследованиях безопасности.

Статус виден агентам по адресу GET /api/v1/status (без авторизации, CORS *). Корректно ведущие себя агенты опрашивают его перед покупками и отступают, когда purchases_paused или agents_paused равно true. Roamzy оставляет за собой право троттлить агентов, игнорирующих эндпоинт статуса.

Есть и более точечная остановка: agents.purchases.paused блокирует только POST /orders, оставляя эндпоинты чтения работоспособными. Полезно, когда нужно разобраться с проблемой на стороне платежей, не отключая информационные API-вызовы.

QR — основная поверхность активации, а не LPA

Большинство агентных сценариев выполняются не на том устройстве, где живёт eSIM. Агентом может быть Claude Desktop на Mac, тогда как eSIM нужно установить на iPhone пользователя. В этом межустройственном случае правильная поверхность — QR: API возвращает qr_image_url (URL PNG через рендерер qrserver.com), агент встраивает его прямо в чат, а пользователь сканирует камерой телефона.

Поле lpa_url тоже возвращается, но полезно лишь когда агент и целевое устройство совпадают — например, агент работает на iPhone пользователя. На iOS 17.4+ и Android 14+ нажатие на URL вида lpa: напрямую открывает системный установщик eSIM. Но для большинства агентных сценариев QR надёжнее.

Это небольшое проектное решение, но оно важно: многие MCP-серверы написаны с ментальной моделью «вкладки браузера», где пользователь сидит рядом с агентом и нажал бы на ссылку. Реальность потребительского использования eSIM межустройственная.

Почему USDT, а не фиат

Криптоплатежи агентно-ориентированы по трём причинам, которые не всегда очевидны:

  1. Нет требований по хранению карт. Хранение данных карт требует соответствия PCI DSS уровня 1, плюс правил карточных сетей о том, как держатель карты должен аутентифицировать каждую транзакцию. Большинство этих правил предполагают, что за клавиатурой человек. Криптовалюта обходит весь этот режим стороной.
  2. Нет риска чарджбэков. Как только транзакция в USDT подтверждена в блокчейне, она окончательна. Нет сценария «держатель карты оспорил эту транзакцию полгода спустя». Для агентной коммерции это важнее, чем для браузерной, потому что человек-пользователь мог и не быть тем, кто проверял и одобрял трату в реальном времени.
  3. Ясность расчётов. Бюджет агента номинирован в USDT. Кошелёк пользователя держит USDT. Мерчант рассчитывается в USDT. Между ними нет конвертации в фиат, нет валютного риска, нет задержки из-за банковских часов. Весь процесс от начала до конца имеет форму API.

Это действительно сужает доступный рынок — большинство путешественников, не близких к крипте, не станут покупать eSIM за USDT. Но для той когорты, что станет (путешественники, близкие к крипте, агентные сценарии, разработчики, автоматизирующие поездки), это убирает целый ворох трения.

Что мы не стали делать (и почему)

Токены со scope в стиле OAuth. У нас один флаг разрешения (покупка / без покупки) вместо детального списка scope. Логика такая: агентам в 2026 году пока не нужно различие «только чтение баланса» или «только чтение eSIM». Если оно понадобится позже, мы добавим scope. Преждевременные модели scope порождают больше багов, чем предотвращают.

Анонимная регистрация в режиме «агент первым». Сейчас агент не может создать пользователя Roamzy на лету; человек должен один раз войти в браузере, чтобы выпустить токен. Это трение. Оно у нас в бэклоге как agent-first flow (анонимное создание пользователя + привязка по magic-link), но мы ждём данных о трафике, прежде чем строить. Если 50% потенциальных пользователей отваливаются на «создать аккаунт» — это сигнал, ради которого стоит оптимизировать; если 5% — нет.

Доставка вебхуков для событий агента. Сейчас агент опрашивает GET /orders/:id для обновлений статуса. Вебхук на основе push был бы эффективнее при масштабировании, но добавляет сложности (агенту нужно поднять эндпоинт, проверять подпись, иметь политику повторов). Опрос с интервалом 5 секунд в течение 10-минутного окна подтверждения USDT — это 120 запросов, дёшево.

Попробуйте сами

Если вы создаёте AI-агента и хотите интегрировать покупку eSIM, самый быстрый путь — MCP-сервер. Установка в Claude Desktop за 60 секунд — см. туториал. Для клиентов без MCP полная спецификация OpenAPI 3.0 находится по адресу /api/v1/openapi.json, интерактивный Swagger UI — по адресу /api/v1/docs, а развёрнутые рекомендации для агентов — по адресу /llms-full.txt.

По более сложным вопросам бот поддержки в Telegram доступен по адресу @roamzy_support_bot.