dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
MCP-серверы для российских и китайских маркетплейсов. Цены, наличие, рейтинги, отзывы и реквизиты продавцов с Wildberries, Ozon, Яндекс Маркета, Детского мира, Авито, AliExpress, Taobao, Мегамаркета, Lamoda, DNS и Ситилинка. Плюс недвижимость с Циана и сравнение цен по всем товарным источникам одним вызовом.
Только чтение. Ключи API, токены и регистрация не нужны — площадки с жёстким анти-ботом читаются через ваш собственный Chrome. Одно исключение по желанию: опциональный MPStats берёт платный токен (MPSTATS_MP_AUTH) — без него всё остальное работает как прежде.
English version below · Первый успешный запрос · Архитектура · Как добавить источник · Про анти-бот
Для проверок в браузере добавлен опциональный режим сохранения вкладки: CHROME_CHALLENGE_HANDOFF_S=120. После завершения проверки повтор того же запроса в той же MCP-сессии продолжает чтение этой вкладки. Поддержка и ограничения описаны в настройке Chrome.
Сервер · Инструментов · Что нужно, чтобы читалось · Что умеет Wildberries · 8 · анонимный HTTP · Поиск, карточки, отзывы, вопросы о товаре, реквизиты продавца, каталог и товары категории Яндекс Маркет · 2 · анонимный HTTP · Цены разных продавцов, разбивка оценок по звёздам, отзывы Детский мир · 3 · анонимный HTTP · Детские товары, наличие в офлайн-магазинах, категории Ozon · 3 · ваш Chrome; с домашнего IP часто и без него · Поиск, карточки, отзывы Авито · 3 · ваш Chrome + российский домашний IP и запросы вразрядку — иначе блок по IP · Поиск объявлений, карточки, репутация продавца Taobao · 2 · ваш Chrome с активным входом в Taobao · Поиск и карточки, цены в юанях Мегамаркет · 2 · ваш Chrome с активным входом — анонимной сессии API отдаёт пусто · Поиск и карточки через мобильный API Lamoda · 2 · карточки: HTTP GraphQL → Chrome при отказе; поиск — Chrome · Поиск, карточки с размерами DNS · 2 · ваш Chrome (Qrator) · Поиск и карточки электроники Ситилинк · 2 · ваш Chrome (Qrator) · Поиск и карточки электроники AliExpress · 2 · ваш Chrome (x5sec) · Поиск и карточки, цены в рублях Циан · 2 · ваш Chrome (WAF по IP) · Недвижимость: поиск по фильтрам (продажа, аренда, посуточно) и карточка объявления Сравнение · 4 · опрашивает всё перечисленное · «Где дешевле?» одним вызовом MPStats · 2 · платный аккаунт MPStats, cookie mp_auth (опционально) · Продажи/остатки/графики за 30 дней по SKU Ozon/WB, остатки по складам (FBS/FBO)
Читается анонимно, без браузера: Wildberries, Яндекс Маркет и Детский мир. Карточки Lamoda сначала читаются по HTTP GraphQL; при отказе нужен ваш Chrome (CDP) для запасного уровня. Остальным нужен ваш залогиненный Chrome (CDP). Taobao и Мегамаркет вдобавок требуют активного входа в саму площадку — без него Taobao упирается в стену логина, а Мегамаркет отдаёт пустой ответ. Авито ещё и блокирует по IP: с датацентрового адреса это глухой отказ, с российского домашнего — работает, если не частить запросами. Запросы к CDP-источникам идут вразрядку: очередь подряд без пауз роняет их (DNS и Taobao в проверке так и деградировали), поэтому коннекторы держат паузу между вызовами сами. Точное состояние из вашей сессии покажет marketplace-mcp doctor.
MPStats стоит особняком: это единственный платный источник. Без MPSTATS_MP_AUTH сервер запускается, но инструменты отвечают auth_missing — поэтому он опционален и подключается по желанию, на остальные тринадцать серверов он не влияет никак.
Всего 39 инструментов в 14 серверах на общем рантайме mcp-core. Плюс объединённый marketplace-mcp, который монтирует всё разом — одна запись в конфиге клиента вместо четырнадцати. Он добавляет свой инструмент marketplace_sources (какие коннекторы поднялись, а какие отвалились и почему), так что в нём 40 инструментов: 39 смонтированных плюс этот.
Проверка 2.5.3: 13 октября с одного адреса selfcheck прошли Ozon, Яндекс Маркет, Детский мир, Авито (поиск, карточка, продавец), Taobao, Ситилинк, AliExpress и Циан; Wildberries отвечает через WB_TRANSPORT=dom. Lamoda, DNS и Мегамаркет честно отвечают «заблокировано», пока человек не пройдёт их проверку в Chrome: куки живут минуты (12 октября после проверки они работали). Дрейфа не найдено, визуальная приёмка не выполнена. Статус источников.
Нужны Python 3.12+ и uv.
Для установки клиента, выбора источников и проверки первого предложения — пошаговый старт. Node нужен только для тестов разработчика.
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" # 2269 офлайн-тестов, сеть не нужнаПроверка живого эндпоинта:
uv run python -c "
import asyncio
from wb_connector.server import wb_selfcheck
print(asyncio.run(wb_selfcheck()).status) # ждём success
"Каждый сервер — консольная команда, поэтому пути в конфиге не зашиваются.
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.
MARKETPLACE_SOURCESОбъединённый сервер монтирует все источники, а описания их инструментов уходят в контекст в каждом запросе. Переменная MARKETPLACE_SOURCES оставляет только перечисленные:
{
"mcpServers": {
"marketplace": {
"command": "uv",
"args": ["run", "--directory", "C:/путь/к/ru-marketplace-mcp", "marketplace-mcp"],
"env": {
"MARKETPLACE_SOURCES": "wildberries,ozon,yandex_market,avito,aliexpress,dns,compare",
},
},
},
}Имена — канонические (wildberries, ozon, yandex_market, detsky_mir, avito, taobao, megamarket, lamoda, dns, citilink, aliexpress, cian, compare, mpstats); короткие псевдонимы wb, ym/yandex, detmir, ali тоже принимаются. Неизвестное имя отклоняется при запуске с перечнем поддерживаемых источников, чтобы опечатка не превратилась в частичный сервер. Переменная не задана или пуста — монтируется всё, как раньше. Отключённые источники видно в marketplace_sources: они попадают в skipped с пометкой, что их сняли, а не что они не импортировались. compare_prices опрашивает ровно тот же набор.
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-mcpCursor — .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, aliexpress-mcp, cian-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Сразу после установки появляются 15 навыков и ни одного MCP-инструмента: обе строки MCP выключены, пока не задана переменная RU_MARKETPLACE_MCP_DIR с путём к клону. Так сделано потому, что смонтированный сервер платится в каждом запросе: рекомендуемый режим сравнения цен стоит ~0,9 тыс. токенов, полный набор — ~13,6 тыс. Включение и полный режим описаны в dsh/README.md.
После подключения перезапустите клиент и прогоните marketplace-mcp doctor. Он запускает канарейку каждого коннектора и отвечает success, drift_detected или inconclusive.
Канарейки *_selfcheck в этом перечне не значатся намеренно: они не публикуются по MCP, потому что диагностика оператора стоила бы модели ~7,5 тыс. токенов в каждом запросе. Запускает их marketplace-mcp doctor — все разом, из командной строки.
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 — у них нет своей выдачи, и инструмент честно об этом говорит вместо пустого списка.
Если с вашего адреса API WB отвечает 403 (так бывает: отказ зависит и от адреса, и от клиента), включите WB_TRANSPORT=dom. Тогда wb_search и wb_card читают отрисованные страницы WB в вашем Chrome через CDP, как DNS и Ситилинк, без обращений к API. В выдаче поиска price_rub — цена по WB Кошельку (price_kind: wallet); карточка отдаёт обычную цену, цену по Кошельку и зачёркнутую раздельно, а также магазин, склад, срок доставки, возврат, гарантию, значок «Оригинал» и строку «Все N предложений от X ₽». Подробности — ANTI_BOT.md.
yandex_*Инструмент · Что делает yandex_search(query, page, limit) · Поиск с обеими ценами, рейтингами, продавцами yandex_card(product_id, include_reviews) · Карточка целиком: разбивка по звёздам и отзывы
Две цены, всегда. price_rub платит любой покупатель. price_with_plus требует подписку Яндекс Плюс и обычно на 25–30% ниже. Интерфейс Яндекса показывает вторую крупным шрифтом, поэтому назвать её без оговорки — значит пообещать цену, которую человек без подписки не получит.
Строка поиска — это оффер из выдачи, а не дефолтный оффер карточки. Один product_id покрывает семейство товаров, и в выдаче может показываться один его представитель, а в карточке по тому же id — другой; сверяйте строки поиска с карточками по sku_id, а не по URL. price_old_rub в строках поиска — зачёркнутая базовая цена, контекст скидки; называть её ценой нельзя.
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_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 первый уровень обычно работает, и браузер не нужен.
Отзывы на Ozon общие для всей карточки-семейства, и соседи по пулу — часто другой товар другого бренда. У карточки масляного радиатора Huter 1500 Вт (SKU 5264146973, рейтинг 4.8 из 356 отзывов) среди 100 вытянутых отзывов не оказалось ни одного о самом Huter: 38 про Ресанту 2000 Вт, 34 про Ресанту 1500 Вт, 5 про Eurolux и так далее — всего 12 товаров в пуле. Поэтому каждый отзыв несёт item_id — SKU того товара, о котором он написан, а ответ дополнительно отдаёт requested_item_id, own_reviews (сколько отзывов действительно об этом SKU) и pool_variants (SKU → название всех товаров пула). rating_score и distribution считаются по пулу, а не по товару: прежде чем делать вывод, отзывы нужно отфильтровать по item_id, а при own_reviews: 0 — честно сказать, что своих отзывов у товара нет.
avito_*Инструмент · Что делает avito_search(query, page, location_id, category_id) · Поиск объявлений через внутренний js/items API avito_card(item_id_or_url) · Страница объявления через CDP: название, цена, описание, число фото, продавец (рейтинг, ссылка на профиль) avito_seller(seller_id_or_url) · Рейтинг продавца, число отзывов; число активных объявлений недоступно (null)
Авито — это объявления, а не каталог: пула отзывов на товар нет, репутация продавца и есть сигнал доверия. Бесплатное/обменное объявление приходит с price_rub: null — никогда не 0, чтобы не оказаться «самым дешёвым» в сравнении. Поиск использует TLS-имперсонацию и ваш Chrome как запасной путь. Карточки читают schema.org Product и блок продавца со страницы объявления через CDP: seller содержит репутацию и ссылку на профиль, если блок доступен. На 2026-10-12 /web/1/js/items игнорирует itemId и userId; разные продавцы получают один общий каталог с totalCount: 1500, а не свои объявления. Поэтому продавцы читаются с отрисованного профиля через CDP с теми же паузами и разрешёнными хостами, что и карточки. Принимаются /brands/, /user//profile и голый ID: 32 шестнадцатеричных символа ведут в brands, остальные ID — в user. Недоступный счётчик active_items остаётся null. Полный URL карточки используется без query/fragment; для ID применяется /{id}: Авито перенаправляет его на страницу объявления. location, posted_at, views остаются null: их нет в Product JSON-LD. Без Product или одновременно без названия и цены возвращается parser_drift, а не пустой успех. Canary проходит цепочку поиск → карточка → профиль продавца. С датацентрового IP возможен 403-файрвол.
taobao_*Инструмент · Что делает taobao_search(query, page) · Поиск по каталогу Taobao taobao_card(item_id_or_url) · Карточка товара
Поиск Taobao — клиентское React-приложение с подписанным mtop API: каждый запрос требует sign, вычисленный из cookie-токена, поэтому анонимного пути нет. Все чтения идут внутри вашего Chrome, где сайт сам подписывает запросы. Цены в юанях (CNY) и не конвертируются: зашитый курс молча устарел бы, так что сравнение с рублёвыми источниками делайте явно.
Эти четыре читаются через ваш Chrome (CDP). Мегамаркет (megamarket_*) — мобильный JSON API из-за ServicePipe, и одного пройденного челленджа мало: анонимной сессии API отдаёт пустой список, нужен активный вход в Мегамаркет. DNS (dns_*) и Ситилинк (citilink_*) — отрисованный DOM из-за Qrator; у всех трёх анонимного пути нет вообще. Lamoda (lamoda_*) наполовину: поиск идёт через Chrome, а карточки сначала читаются через HTTP GraphQL (tier 1). При ответе не 200 или httpx.TransportError выполняется ровно один GraphQL POST внутри страницы вашего Chrome через CDP (tier 2); успешный fallback отмечен tier_used: graphql_cdp. HTTP 200 не вызывает CDP. Причина: 2026-10-12 GraphQL по HTTPS отвечал циклом 307. Chrome с CDP (scripts/start_chrome_cdp.sh) нужен остальным всегда, а карточкам Lamoda — только для запасного уровня.
Всего через CDP ходят девять источников — эти плюс Taobao, AliExpress, Циан, Ozon и Авито. У Ozon, а также у поиска и продавцов Авито Chrome — запасной уровень, когда анонимный tier 1 упёрся в челлендж; карточки Авито всегда требуют CDP. marketplace-mcp doctor из вашего браузера скажет, какие эндпоинты подтверждены.
aliexpress_*Инструмент · Что делает aliexpress_search(query) · Поиск: до 48 карточек с ценами в рублях aliexpress_card(item_id_or_url) · Карточка: цена, рейтинг, число заказов
Читается через ваш Chrome (CDP): x5sec ставит капчу анонимным клиентам, поэтому коннектор садится на страницу поиска (её не челленджат) и открывает карточку новой вкладкой из неё. Цены в рублях и участвуют в compare_prices. Карточка с названием, но б