ru-marketplace-mcp
MCP-серверы для российских и китайских маркетплейсов. Цены, наличие,
рейтинги, отзывы и реквизиты продавцов с Wildberries, Ozon, Яндекс Маркета,
Детского мира, Авито, Taobao, Мегамаркета, Lamoda, DNS и Ситилинка. Плюс
сравнение цен по всем источникам одним вызовом.
Только чтение. Ключи API, токены и регистрация не нужны — площадки с жёстким
анти-ботом читаются через ваш собственный Chrome. Одно исключение по желанию:
опциональный MPStats берёт платный токен (MPSTATS_MP_AUTH) — без него всё
остальное работает как прежде.
English version below · Архитектура ·
Как добавить источник · Про анти-бот
Что внутри
| Сервер | Инструментов | Что нужно, чтобы читалось | Что умеет |
|---|---|---|---|
| Wildberries | 9 | анонимный HTTP | Поиск, карточки, отзывы, вопросы о товаре, реквизиты продавца, каталог и товары категории |
| Яндекс Маркет | 3 | анонимный HTTP | Цены разных продавцов, разбивка оценок по звёздам, отзывы |
| Детский мир | 4 | анонимный HTTP | Детские товары, наличие в офлайн-магазинах, категории |
| Ozon | 4 | ваш Chrome; с домашнего IP часто и без него | Поиск, карточки, отзывы |
| Авито | 4 | ваш Chrome + российский домашний IP и запросы вразрядку — иначе блок по IP | Поиск объявлений, карточки, репутация продавца |
| Taobao | 3 | ваш Chrome с активным входом в Taobao | Поиск и карточки, цены в юанях |
| Мегамаркет | 3 | ваш Chrome с активным входом — анонимной сессии API отдаёт пусто | Поиск и карточки через мобильный API |
| Lamoda | 3 | карточки анонимно (GraphQL), поиск — ваш Chrome | Поиск, карточки с размерами |
| DNS | 3 | ваш Chrome (Qrator) | Поиск и карточки электроники |
| Ситилинк | 3 | ваш Chrome (Qrator) | Поиск и карточки электроники |
| Сравнение | 2 | опрашивает всё перечисленное | «Где дешевле?» одним вызовом |
| MPStats | 3 | платный аккаунт MPStats, cookie mp_auth (опционально) |
Продажи/остатки/графики за 30 дней по SKU Ozon/WB, остатки по складам (FBS/FBO) |
Читается анонимно, без браузера: Wildberries, Яндекс Маркет, Детский мир и
карточки Lamoda. Остальным нужен ваш залогиненный Chrome (CDP). Taobao и
Мегамаркет вдобавок требуют активного входа в саму площадку — без него Taobao
упирается в стену логина, а Мегамаркет отдаёт пустой ответ. Авито ещё и блокирует
по IP: с датацентрового адреса это глухой отказ, с российского домашнего — работает,
если не частить запросами. Запросы к CDP-источникам идут вразрядку: очередь
подряд без пауз роняет их (DNS и Taobao в проверке так и деградировали), поэтому
коннекторы держат паузу между вызовами сами. Точное состояние из вашей сессии
покажет marketplace-mcp doctor.
MPStats стоит особняком: это единственный платный источник. БезMPSTATS_MP_AUTH сервер запускается, но инструменты отвечают auth_missing —
поэтому он опционален и подключается по желанию, на остальные двенадцать
серверов он не влияет никак.
Всего 33 инструмента в 12 серверах на общем рантайме mcp-core. Плюс объединённыйmarketplace-mcp, который монтирует всё разом — одна запись в конфиге клиента
вместо двенадцати. Он добавляет свой инструмент marketplace_sources (какие коннекторы
поднялись, а какие отвалились и почему), так что в нём 34 инструмента: 33
смонтированных плюс этот.
Быстрый старт
Нужны Python 3.12+ и uv.
git clone https://github.com/Vladimir-Human/ru-marketplace-mcp.git
cd ru-marketplace-mcp
uv sync --all-packages
uv run pytest -q -m "not live and not cdp" # 1182 офлайн-тестов, сеть не нужна
Проверка живого эндпоинта:
uv run python -c "
import asyncio
from wb_connector.server import wb_selfcheck
print(asyncio.run(wb_selfcheck()).status) # ждём success
"
Подключение к MCP-клиенту
Каждый сервер — консольная команда, поэтому пути в конфиге не зашиваются.
Claude Desktop — claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Проще всего подключить одну запись — объединённый сервер монтирует все
источники разом, а имена инструментов (wb_search, avito_seller, …) не
меняются:
{
"mcpServers": {
"marketplace": {
"command": "uv",
"args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "marketplace-mcp"],
},
},
}
Если нужны отдельные серверы, marketplace-mcp install claude напечатает
готовый блок для вставки. Путь к вашему checkout там уже подставлен: заглушку/path/to/ru-marketplace-mcp править руками не придётся. При установке из wheel
вместо путей печатаются консольные команды на PATH. Неизвестное имя клиента
(допустимы claude, claude-code, cursor, dsh) команда отклоняет с пояснением и
кодом возврата 2 — молча подставить блок для Claude она не может. Минимальный
вариант вручную:
{
"mcpServers": {
"wildberries": {
"command": "uv",
"args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "wb-mcp"],
},
"ozon": {
"command": "uv",
"args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "ozon-mcp"],
},
"compare-prices": {
"command": "uv",
"args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "compare-mcp"],
},
},
}
Путь пишите с прямыми слешами / или двойными обратными \\. Полный список
команд — wb-mcp, ozon-mcp, yandex-mcp, detmir-mcp, avito-mcp,taobao-mcp, megamarket-mcp, lamoda-mcp, dns-mcp, citilink-mcp,compare-mcp, marketplace-mcp.
Claude Code
claude mcp add wildberries -- uv run --directory /путь/к/ru-marketplace-mcp wb-mcp
claude mcp add yandex-market -- uv run --directory /путь/к/ru-marketplace-mcp yandex-mcp
claude mcp add detsky-mir -- uv run --directory /путь/к/ru-marketplace-mcp detmir-mcp
claude mcp add ozon -- uv run --directory /путь/к/ru-marketplace-mcp ozon-mcp
claude mcp add compare-prices -- uv run --directory /путь/к/ru-marketplace-mcp compare-mcp
Cursor — .cursor/mcp.json
{
"mcpServers": {
"compare-prices": {
"command": "uv",
"args": ["run", "--directory", "/путь/к/ru-marketplace-mcp", "compare-mcp"],
},
},
}
Другой stdio-клиент
Запустите uv run --directory /путь/к/репозиторию <команда>, где команда — одна изwb-mcp, ozon-mcp, yandex-mcp, detmir-mcp, compare-mcp. Серверы говорят по
JSON-RPC через stdin и stdout, диагностику пишут в stderr. Опциональныйmpstats-mcp запускается так же, с MPSTATS_MP_AUTH в окружении.
DeepSeek Harness (dsh) — плагин-бандл
В dsh это не запись mcpServers, а слой профиля. Бандл лежит в подкаталогеdsh/ и ставится штатным менеджером плагинов (pnpm нужен на PATH):
dsh plugin --profile web add github:Vladimir-Human/ru-marketplace-mcp#path:/dsh
Сразу после установки появляются 13 навыков и ни одного MCP-инструмента: обе
строки MCP выключены, пока не задана переменная RU_MARKETPLACE_MCP_DIR с путём к
клону. Так сделано потому, что смонтированный сервер платится в каждом запросе:
рекомендуемый режим сравнения цен стоит ~0,9 тыс. токенов, полный набор — ~13 тыс.
Включение и полный режим описаны в dsh/README.md.
После подключения перезапустите клиент и прогоните marketplace-mcp doctor. Он
запускает канарейку каждого коннектора и отвечает success, drift_detected илиinconclusive.
Инструменты
Канарейки *_selfcheck в этом перечне не значатся намеренно: они не публикуются
по MCP, потому что диагностика оператора стоила бы модели ~7,5 тыс. токенов в
каждом запросе. Запускает их marketplace-mcp doctor — все разом, из командной
строки.
Wildberries — wb_*
| Инструмент | Что делает |
|---|---|
wb_search(query, page) |
Поиск по тексту, до 100 товаров на страницу с ценами и остатками |
wb_card(nm_ids) |
Пакетный запрос до 100 известных SKU |
wb_root_info(nm_id) |
Находит imt_id (нужен для отзывов) и цветовые варианты |
wb_reviews(imt_id, limit, sort) |
Пул отзывов. Ключ — imt_id, а не nm_id |
wb_questions(imt_id, limit, skip, answered_only) |
Вопросы покупателей и ответы продавца. Тоже по imt_id |
wb_seller(supplier_id) |
Юрлицо, ИНН, КПП, ОГРН, юридический адрес |
wb_categories(root, max_depth) |
Дерево каталога с шардами и запросами самого WB |
wb_category_products(shard, query, page, sort, dest) |
Товары категории по shard и query из wb_categories |
wb_seller отвечает на вопрос, который карточка товара скрывает: кто на самом деле
продаёт? Возвращает зарегистрированное юрлицо и налоговые номера. Так отличают
официальный магазин бренда от перекупщика с похожим названием.
wb_questions закрывает другой пробел. Отзывы говорят, каково владеть товаром;
вопросы уточняют, что это вообще за товар — «10 или 16 ампер», «кабель в комплекте?».
Ответ продавца часто единственное публичное утверждение об этом. Пул общий для всех
вариантов товара, ключ — imt_id из wb_root_info.
wb_category_products замыкает связку с wb_categories: та отдаёт shard и query,
это — товары по ним. Формат элементов совпадает с wb_search, поэтому обход категорий
и текстовый поиск сравнимы напрямую. Часть крупных разделов WB помечает шардомblackhole — у них нет своей выдачи, и инструмент честно об этом говорит вместо
пустого списка.
Яндекс Маркет — yandex_*
| Инструмент | Что делает |
|---|---|
yandex_search(query, page, limit) |
Поиск с обеими ценами, рейтингами, продавцами |
yandex_card(product_id, include_reviews) |
Карточка целиком: разбивка по звёздам и отзывы |
Две цены, всегда. price_rub платит любой покупатель. price_with_plus
требует подписку Яндекс Плюс и обычно на 25–30% ниже. Интерфейс Яндекса показывает
вторую крупным шрифтом, поэтому назвать её без оговорки — значит пообещать цену,
которую человек без подписки не получит.
rating_stars даёт распределение вида {1: 10, 2: 3, 3: 10, 4: 19, 5: 502}. Из
него видно, честная ли средняя 4.8 или за ней прячется кучка единиц.
Детский мир — detmir_*
| Инструмент | Что делает |
|---|---|
detmir_categories(parent, limit, region) |
Дерево каталога. Начинать отсюда |
detmir_category(alias, limit, offset, region) |
Товары категории с настоящим счётчиком |
detmir_card(product_id, region) |
Цена, рейтинг, наличие онлайн и в магазинах |
Регион задаётся на каждый вызов. Цены и особенно наличие в офлайн-магазинах
сильно зависят от города: один и тот же товар лежал в 152 магазинах Москвы, 37
Петербурга и 2 Хабаровска. Параметр region перекрывает DETMIR_REGION, так что
города можно сравнивать в одной сессии.
Текстового поиска здесь нет, и это намеренно. API Детского мира молча игнорирует
любые текстовые фильтры и возвращает весь каталог на 300 тысяч позиций, а сайтовый
роут поиска отдаёт 404 с промо-карусселью. Инструмент поиска возвращал бы уверенно
неверные товары, поэтому навигация идёт через категории. Подробности в
docs/ANTI_BOT.md.
Ozon — ozon_*
| Инструмент | Что делает |
|---|---|
ozon_search(query) |
Поиск по тексту |
ozon_card(sku_or_path) |
Карточка товара |
ozon_reviews(sku_or_path, limit, sort) |
Отзывы |
Ozon отклоняет датацентровый трафик, поэтому коннектор двухуровневый. Сначала
TLS-имперсонация. Если Cloudflare выдаёт челлендж, запрос выполняется внутри вашего
залогиненного Chrome через DevTools Protocol. Ничего не хранится: вход выполняете вы
сами, в браузере, который контролируете. Настройка описана в
docs/CDP_SETUP.md.
С российского домашнего IP первый уровень обычно работает, и браузер не нужен.
Авито — avito_*
| Инструмент | Что делает |
|---|---|
avito_search(query, page, location_id, category_id) |
Поиск объявлений через внутренний js/items API |
avito_card(item_id_or_url) |
Одно объявление: цена, описание, просмотры, продавец |
avito_seller(seller_id_or_url) |
Рейтинг продавца, число отзывов, активные объявления |
Авито — это объявления, а не каталог: пула отзывов на товар нет, репутация
продавца и есть сигнал доверия. Бесплатное/обменное объявление приходит сprice_rub: null — никогда не 0, чтобы не оказаться «самым дешёвым» в
сравнении. С датацентрового IP Авито отвечает 403-файрволом, поэтому коннектор
двухуровневый: TLS-имперсонация, дальше ваш Chrome (как у Ozon).
Taobao — taobao_*
| Инструмент | Что делает |
|---|---|
taobao_search(query, page) |
Поиск по каталогу Taobao |
taobao_card(item_id_or_url) |
Карточка товара |
Поиск Taobao — клиентское React-приложение с подписанным mtop API: каждый запрос
требует sign, вычисленный из cookie-токена, поэтому анонимного пути нет.
Все чтения идут внутри вашего Chrome, где сайт сам подписывает запросы. Цены в
юанях (CNY) и не конвертируются: зашитый курс молча устарел бы, так что
сравнение с рублёвыми источниками делайте явно.
Мегамаркет, Lamoda, DNS, Ситилинк
Эти четыре читаются через ваш Chrome (CDP). Мегамаркет (megamarket_*) — мобильный
JSON API из-за ServicePipe, и одного пройденного челленджа мало: анонимной сессии
API отдаёт пустой список, нужен активный вход в Мегамаркет. DNS (dns_*) и Ситилинк
(citilink_*) — отрисованный DOM из-за Qrator; у всех трёх анонимного пути нет вообще.
Lamoda (lamoda_*) наполовину: карточки берутся анонимно через GraphQL, а поиск —
через Chrome. Chrome с CDP (scripts/start_chrome_cdp.sh) нужен всем, кроме карточек
Lamoda.
Всего через CDP ходят семь источников — эти плюс Ozon и Авито, где Chrome лишь
запасной уровень: их tier 1 обычно отвечает, а браузер включается, когда анонимный
уровень упёрся в челлендж. marketplace-mcp doctor из вашего браузера скажет, какие
эндпоинты подтверждены.
Сравнение цен — compare_*
| Инструмент | Что делает |
|---|---|
compare_prices(query, per_source_limit, sources) |
Все маркетплейсы сразу, с ранжированием |
compare_sources() |
Какие маркетплейсы доступны в этой установке |
compare_prices("кроссовки мужские")
wildberries 712 ₽ Кроссовки изи дышащие спортивные
wildberries 814 ₽ Зимние кроссовки теплые с мехом
yandex_market 2499 ₽ Кеды A-LOW
yandex_market 3480 ₽ Кеды
дешевле всего: wildberries 712 ₽, разброс 5858 ₽, complete: true
Маркетплейсы опрашиваются параллельно, и каждый отчитывается сам за себя. Если один
заблокирован, сравнение не рушится: complete: false вместе с source_outcomes
покажет, что именно вы видите. Подписочные цены в ранжировании не участвуют.
Совпадающие предложения по паре (источник, id товара) схлопываются, так что один
и тот же товар не занимает два места в ранжировании.
У каждого предложения есть currency (строчный ISO-код, по умолчанию rub) иprice_native — цена в этой валюте, как её показывает маркетплейс. Для российских
источников она совпадает с price_rub; у Taobao в ней лежит цена в юанях, которуюprice_rub намеренно оставляет пустой. Раньше юаневую цену забирали и молча
выбрасывали, и строка Taobao приходила с пустой ценой без намёка, что цена вообще
есть. Теперь юань виден, но в рублёвом ранжировании по-прежнему не участвует: вwarnings появляется foreign_currency: … с числом исключённых предложений и
причиной. Конвертировать здесь значило бы зашить курс, который молча устареет, —
пересчёт за вами.
MPStats — mpstats_*
Аналитика продаж и остатков по SKU Ozon и Wildberries через плагин MPStats.
В отличие от всех остальных коннекторов, этот опционален и требует платный
аккаунт MPStats: авторизация — одна cookie mp_auth (JWT из залогиненной
сессии плагина на mpstats.io), задаётся переменной MPSTATS_MP_AUTH. Без неё
инструменты возвращают auth_missing, а сервер запускается как обычно — ни на
что другое это не влияет.
| Инструмент | Что делает |
|---|---|
mpstats_item(skus, place, oz_fbs=True) |
Аналитика за 30 дней по до 100 SKU: заказы, цена, остатки, графики по дням, продавец/бренд |
mpstats_warehouses(skus, place) |
Остатки по складам: FBS (склад продавца) и FBO (склад маркетплейса), last_update |
place — ozon или wildberries. Графики длиной 30, от старых к новым:
последняя ненулевая ячейка — текущая цена или остаток. Цена и остаток при
сплошь нулевом графике ведут себя намеренно по-разному: цена становится None
(ложный 0 выиграл бы любое сравнение «где дешевле»), а остаток — 0, потому
что «нулевой остаток» это осмысленное показание, а не отсутствие данных. Пустой
график даёт None в обоих случаях. Ноль в отдельной ячейке — «нет данных за тот
день», а не «значение было нулевым», поэтому сумму за окно считайте по графику. Отсутствие
токена и транспортные сбои selfcheck отчитывает как inconclusive, не drift:
гоняться за дрейфом схемы, которого не было, не нужно. Токен — секрет платного
аккаунта с квотой: не логируйте и не коммитьте его.
Навыки для агента
У каждого коннектора — свой навык в skills/, тринадцать штук на тринадцать
серверов. Навык это не пересказ README: он объясняет агенту, когда за этот
источник вообще браться, чего у источника нет, и каким его ответам нельзя верить
без второго взгляда.
| Навык | Сервер |
|---|---|
skills/wb-connector |
wb-mcp |
skills/ozon-connector |
ozon-mcp |
skills/yandex-connector |
yandex-mcp |
skills/detmir-connector |
detmir-mcp |
skills/avito-connector |
avito-mcp |
skills/taobao-connector |
taobao-mcp |
skills/megamarket-connector |
megamarket-mcp |
skills/lamoda-connector |
lamoda-mcp |
skills/dns-connector |
dns-mcp |
skills/citilink-connector |
citilink-mcp |
skills/compare-prices |
compare-mcp |
skills/mpstats-connector |
mpstats-mcp |
skills/marketplace |
marketplace-mcp |
mcp-core — общий рантайм под остальными серверами. Своего навыка у него нет.
Соответствие проверяется тестом
(packages/marketplace-connector/tests/test_skills_parity.py): новый коннектор
без навыка роняет прогон, как и навык, который называет несуществующий
инструмент или забыл существующий. До этого теста навык DNS почти год советовал
формат ссылки /product/<24-hex>/ — тот самый шаблон, который чинили как баг.
Скиллы едут в Docker-образ (/app/skills/), но в колёсах их нет: skills/
лежит в корне репозитория. Ставите с PyPI — возьмите навыки
из репозитория отдельно.
Настройка
Все параметры задаются переменными окружения с префиксом коннектора. Все
необязательные.
| Префикс | Основные параметры |
|---|---|
WB_ |
TIMEOUT, MIN_GAP, DEFAULT_DEST, NET_RETRIES, MAX_BODY_BYTES, CACHE_TTL, PROXY |
YANDEX_ |
TIMEOUT, MIN_GAP, CACHE_TTL, PROXY |
DETMIR_ |
REGION (RU-MOW, RU-SPE и другие), CACHE_TTL, PROXY |
OZON_ |
TIMEOUT, MIN_GAP, IMPERSONATE, CACHE_TTL, PROXY |
AVITO_ |
TIMEOUT, MIN_GAP, IMPERSONATE, CACHE_TTL, PROXY, LOCATION_ID |
TAOBAO_ |
TIMEOUT, MIN_GAP, CACHE_TTL, PROXY |