Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

Реверс-инжиниринг приватного API и интеграция с ИИ: создание MCP-сервера для Яндекс Лавки с защитой от финансовых рисков

Пошаговый разбор реверс-инжиниринга приватного API Яндекс Лавки: извлечение CSRF-токенов из HTML, обработка конкурентного доступа через optimistic locking и HTT

Коротко

Что будет в материале

  1. 01

    Зачем ИИ-ассистенту доступ к Яндекс Лавке: постановка задачи

  2. 02

    Архитектура решения: MCP-сервер как прослойка между Claude и Яндекс Лавкой

  3. 03

    Разведка боем: как мы исследовали приватное API Яндекс Лавки

  4. 04

    Работа с корзиной: optimistic locking и битва с HTTP 409

Зачем ИИ-ассистенту доступ к Яндекс Лавке: постановка задачи

Представьте: вы работаете над сложным проектом, время поджимает, а холодильник пуст. Вместо того чтобы отвлекаться на выбор продуктов и оформление доставки, вы пишете Claude: «Добавь в корзину молоко, яйца, хлеб и пачку кофе, который я заказывал в прошлый раз, и оформи заказ на ближайшее время». Ассистент выполняет задачу за секунды, пока вы остаётесь в потоке.

Звучит как сценарий из будущего, но технически это реализуемо прямо сейчас. Проблема в том, что у Яндекс Лавки нет публичного API. Есть только внутренний, недокументированный, завязанный на CSRF-токены, сессии браузера и механизмы конкурентного доступа к корзине. Чтобы научить ИИ управлять заказами, нужен промежуточный слой, который возьмёт на себя всю грязную работу: аутентификацию, парсинг, валидацию и защиту от ошибочных транзакций.

Этим слоем стал MCP-сервер на Python с FastMCP и httpx. Он принимает команды от Claude, транслирует их в запросы к приватному API Яндекс Лавки и возвращает структурированный ответ. В статье разберём полный цикл: от разведки эндпоинтов до двухшаговой процедуры подтверждения заказа, которая блокирует финансовые риски.

Архитектура решения: MCP-сервер как прослойка между Claude и Яндекс Лавкой

Model Context Protocol (MCP) определяет стандарт взаимодействия между ИИ-ассистентом и внешними инструментами. Ассистент не делает HTTP-запросы сам, он вызывает инструменты, предоставленные MCP-сервером, получает структурированный ответ и принимает решение на основе фактов, а не сырого HTML.

Схема взаимодействия линейна: Claude формирует вызов инструмента (например, search_products или checkout), MCP-сервер на FastMCP обрабатывает его, выполняет серию запросов к API Яндекс Лавки через httpx и возвращает результат. Сервер изолирует всю чувствительную логику: хранение сессионных кук, извлечение CSRF-токенов, повторные попытки при конфликтах версий корзины.

Ключевые компоненты сервера: менеджер сессий с автоматическим обновлением токенов, обработчик корзины с optimistic locking, двухшаговая процедура оформления заказа с перепроверкой суммы и модуль безопасного деплоя через OAuth.

Почему не прямой вызов API из промпта: ограничения и риски

Передать Claude сырой HTTP-запрос с токенами в промпте - значит слить сессионные данные в логи провайдера. Даже если отбросить безопасность, остаётся проблема нестабильности: ассистент должен парсить HTML, извлекать CSRF-токены из мета-тегов, обрабатывать редиректы и HTTP 409 Conflict. Одна ошибка в цепочке - и заказ либо не оформлен, либо оформлен дважды.

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

Разведка боем: как мы исследовали приватное API Яндекс Лавки

Первый шаг - перехват трафика между браузером и сервером. Инструментарий стандартный: вкладка Network в Chrome DevTools с фильтрацией по XHR/Fetch, копирование запросов как cURL и последующий анализ в Insomnia. Яндекс Лавка использует классическую архитектуру одностраничного приложения: HTML-страница загружает JavaScript-бандл, который дёргает REST API с JSON-ответами.

После авторизации в браузере мы получили три ключевые конечные точки: поиск товаров (/api/v1/search), управление корзиной (/api/v1/cart) и оформление заказа (/api/v1/checkout). Каждый запрос требовал заголовок X-CSRF-Token, значение которого обнаружилось не в куках и не в ответах API, а в HTML-разметке главной страницы.

Извлечение CSRF-токенов из HTML: код и подводные камни

CSRF-токен Яндекс Лавки встроен в мета-тег <meta name="csrf-token" content="..."> на главной странице. Чтобы получить его, сервер должен эмулировать браузерный запрос: загрузить HTML, распарсить мета-теги и извлечь значение. Реализация на httpx выглядит так:

import httpx
from bs4 import BeautifulSoup

async def get_csrf_token(client: httpx.AsyncClient) -> str:
    response = await client.get("https://lavka.yandex.ru")
    soup = BeautifulSoup(response.text, "html.parser")
    meta = soup.find("meta", attrs={"name": "csrf-token"})
    if not meta:
        raise ValueError("CSRF token not found in HTML")
    return meta["content"]

Тонкий момент: токен привязан к сессии и обновляется при каждом входе на сайт. Если сервер долго работает, токен может протухнуть. Решение - автоматическое обновление при получении HTTP 403: повторный запрос к главной странице, извлечение нового токена и ретрай исходного запроса. Ещё одна проблема: Яндекс Лавка проверяет заголовок Referer. Если его нет или он не совпадает с доменом, запрос отклоняется даже с валидным токеном.

Работа с корзиной: optimistic locking и битва с HTTP 409

API корзины Яндекс Лавки реализует optimistic locking - механизм конкурентного доступа, при котором каждый запрос на изменение должен содержать актуальную версию корзины. Версия передаётся в заголовке If-Match или в теле запроса как поле version. Если два клиента одновременно меняют корзину, сервер принимает первый запрос, увеличивает версию, а на второй отвечает HTTP 409 Conflict.

Для MCP-сервера это создаёт проблему: пользователь может параллельно добавлять товары через веб-интерфейс, пока Claude формирует заказ. Без обработки 409 ассистент получит ошибку и не сможет завершить операцию. Решение - цикл с повторными попытками:

async def add_to_cart(client, product_id, quantity, max_retries=3):
    for attempt in range(max_retries):
        cart = await client.get("/api/v1/cart")
        version = cart.json()["version"]
        response = await client.post(
            "/api/v1/cart/items",
            json={"product_id": product_id, "quantity": quantity, "version": version},
        )
        if response.status_code != 409:
            return response
        await asyncio.sleep(0.5 * (attempt + 1))  # экспоненциальная задержка
    raise RuntimeError("Failed to add item after retries")

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

«Фантомные» товары: почему товар есть в поиске, но не добавляется в корзину

Поиск Яндекс Лавки возвращает товары со всех доступных складов в зоне доставки, но не все из них можно добавить в корзину. Товар может числиться в выдаче, но отсутствовать на складе, обслуживающем конкретный адрес. Или быть доступным, но с другим идентификатором (product_id отличается от search_result_id).

Решение - двухэтапная проверка доступности. После получения результатов поиска сервер запрашивает /api/v1/products/{id}/availability с привязкой к адресу доставки. Товары с нулевым остатком или недоступные для выбранного адреса фильтруются до того, как попадут в ответ ассистенту. Это исключает ситуацию, когда Claude предлагает пользователю товар, который невозможно заказать.

Безопасное оформление заказа: двухшаговая процедура с перепроверкой

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

Шаг первый - предварительный расчёт (GET /api/v1/checkout/preview). Сервер получает финальную сумму, состав заказа и версию корзины, которую зафиксирует платёжный шлюз. Эти данные возвращаются Claude, и ассистент показывает их пользователю для подтверждения. Никакие изменения корзины на этом этапе уже не допускаются.

Шаг второй - подтверждение (POST /api/v1/checkout/confirm). Сервер отправляет запрос только при точном совпадении ожидаемой суммы и версии корзины с теми, что были получены на первом шаге. Если за время между preview и confirm корзина изменилась (пользователь добавил товар через веб), сервер детектирует расхождение и возвращает ошибку, не выполняя платёж.

async def checkout(client, expected_total, expected_version):
    preview = await client.get("/api/v1/checkout/preview")
    data = preview.json()
    if data["total"] != expected_total or data["version"] != expected_version:
        raise ValueError("Cart changed since preview - aborting checkout")
    confirm = await client.post("/api/v1/checkout/confirm", json={
        "version": expected_version,
        "payment_method": resolve_payment_method(data["payment_methods"]),
    })
    return confirm.json()

Резолвинг способа оплаты: как не дать ИИ выбрать неверный метод

Яндекс Лавка возвращает список доступных способов оплаты: карта, Apple Pay, баллы Плюса, наличные. Если ассистент может выбрать любой, есть риск списания с нежелательного источника. MCP-сервер реализует детерминированный резолвинг: приоритет задаётся в конфигурации (например, «только карта»), и сервер выбирает первый подходящий метод из списка. Если заданный метод недоступен, заказ не оформляется - fallback на другой источник не происходит без явного подтверждения пользователя.

Удаленный деплой и безопасность: OAuth и защита секретов

MCP-сервер, имеющий доступ к аккаунту Яндекс Лавки с привязанной картой, требует такого же уровня защиты, как платёжный шлюз. Токены сессии, CSRF-токены и куки не должны храниться в коде или логах. Все секреты вынесены в переменные окружения и загружаются через python-dotenv.

Удалённый доступ к серверу организован через OAuth 2.0 с короткоживущими токенами. При деплое на сервер администратор проходит поток Authorization Code Grant, получает access token с ограниченным сроком жизни и refresh token для продления сессии. Токены хранятся в зашифрованном виде, доступ к эндпоинтам MCP-сервера ограничен по IP (whitelist).

Дополнительный уровень защиты - аудит всех операций. Каждый вызов инструмента логируется с таймстемпом, ID операции и результатом. Логи не содержат чувствительных данных (суммы логируются, но не полные данные карты), и ротируются с retention-периодом в 7 дней.

Уроки, извлеченные из проекта: что пошло не так и как мы это исправили

Первый прототип падал каждые 40 минут. Причина - протухание сессионных кук, которые Яндекс Лавка инвалидирует по таймауту неактивности. Решением стал фоновый пинг раз в 15 минут: запрос к главной странице с обновлением CSRF-токена и кук.

Вторая проблема - изменение HTML-разметки. Через неделю после запуска парсер CSRF-токена сломался: Яндекс Лавка переименовала мета-тег с csrf-token на csrf. После этого мы добавили резервный метод извлечения токена из JavaScript-объекта window.__INITIAL_STATE__, который парсится регулярным выражением из HTML. Два метода лучше, чем один.

Третья - гонки состояний корзины при тестировании. Два параллельных вызова add_to_cart от Claude создавали конфликт версий, который разрешался только после трёх ретраев. Добавили очередь запросов на уровне сервера: операции с корзиной сериализуются, параллельные вызовы встают в очередь и выполняются последовательно.

Мониторинг построили на связке Prometheus + Grafana: метрики по латентности запросов к API Лавки, количеству HTTP 409 и успешности оформления заказов. Алерты настроены на резкий рост ошибок аутентификации (значит, изменился формат токенов) и падение success rate checkout ниже 95%.

Юридические и этические аспекты реверс-инжиниринга

Реверс-инжиниринг приватного API находится в серой зоне. Условия использования Яндекс Лавки запрещают автоматизированный доступ к сервису вне официальных клиентов. Аккаунт, замеченный в автоматических запросах, может быть заблокирован. Перед использованием MCP-сервера оцените риски: для исследовательских целей и личного использования вероятность блокировки ниже, чем при коммерческой эксплуатации.

API Яндекс Лавки меняется без предупреждения. Эндпоинты могут быть переименованы, форматы ответов изменены, CSRF-защита усилена. Сервер требует регулярного обслуживания и мониторинга. Рассматривайте это решение как экспериментальное, а не production-grade.

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

Если вы проектируете AI-агента с нуля, изучите архитектуру самописного AI-агента: оркестрация LLM, память, инструменты и обработка ошибок. Для оптимизации расходов на LLM полезен кейс миграции с Claude Sonnet на Qwen 3.5 Flash, где счёт сократился с $2000 до $30 в месяц. При работе с промптами для агентных сценариев пригодятся практические стратегии управления промптами для LLM.

Подписаться на канал