Releases: Vladimir-Human/ru-marketplace-mcp
Release list
ru-marketplace-mcp v1.5.1 — official MCP Registry stdio OCI package
RELEASE NOTES — v1.5.1 (2026-08-16)
Патч для официального MCP-реестра: v1.5.1 публикует OCI-образ, который клиент
может запустить как stdio-сервер через docker run --rm -i, и добавляет
автоматическую публикацию в реестр на каждый тег. Основной функциональный
релиз — v1.5.0; здесь меняется только канал доставки.
Гейт выпуска
| Проверка | Результат |
|---|---|
ruff check + ruff format --check |
зелёный |
mypy (host / win32 / darwin) |
87 файлов, 0 ошибок |
pytest -m "not live and not cdp" |
1181 passed, 1 skipped (1182) |
check_versions.py 1.5.1 |
72 места согласованы |
check_test_count.py / check_no_print.py / uv lock --check |
зелёные |
e2e_stdio_check.py |
13/13 локальных серверов |
| Сборка и публикация OCI + реестр | ✅ run 31942741535, success; запись видна в API реестра |
Что вошло в патч
Добавлено
Dockerfile.stdio— вариант образа для MCP-клиентов:MCP_TRANSPORT=stdio,
CMD ["marketplace-mcp"](34 инструмента), метка
io.modelcontextprotocol.server.nameдля верификации владения в реестре.scripts/e2e_stdio_check_docker.py— настоящая сессия MCP через
docker run --rm -i: initialize → tools/list (ровно 34) → вызов
marketplace_sources(12 смонтированных источников). Это закрывает
прежнее «Docker не проверен» из v1.5.0 по крайней мере для stdio-пути..github/workflows/mcp-registry-publish.yml— на тегv*собирает и
публикует образ в GHCR, прогоняет docker-stdio probe, затем
mcp-publisher login github-oidcиmcp-publisher publishдля официального
MCP-реестра.server.json— добавлен OCI-пакет
ghcr.io/vladimir-human/ru-marketplace-mcp:1.5.1сruntimeHint: docker;
файл проходит JSON Schema2025-12-11без ошибок.
Изменено
- Версия всех 72 объявлений поднята на 1.5.1, включая пакеты workspace,
uv.lock, dsh-бандл и Docker-теги.
Проверка публикации
Факт: workflow mcp-registry-publish для тега v1.5.1 завершился success
(run 31942741535); API реестра возвращает сервер io.github.Vladimir-Human/ru-marketplace-mcp версии 1.5.1 с OCI-пакетом.
ru-marketplace-mcp v1.5.0 — DeepSeek Harness bundle and a 2.9x MCP context-cost cut
RELEASE NOTES — v1.5.0 (2026-08-16)
Главное: проект стал устанавливаемым в DeepSeek Harness, а постоянная цена
MCP-контекста измерена и снижена втрое. Ветка прошла полный CI на трёх ОС.
Гейт выпуска
| Проверка | Результат |
|---|---|
ruff check + ruff format --check |
зелёный |
mypy (host / win32 / darwin) |
87 файлов, 0 ошибок |
pytest -m "not live and not cdp" |
1181 passed, 1 skipped (1182) |
check_no_print.py |
записей в stdout нет |
check_versions.py 1.5.0 |
72 места согласованы |
check_test_count.py |
1182 в 7 местах |
e2e_stdio_check.py |
13/13 серверов, все v1.5.0 |
| CI Ubuntu/Windows/macOS × Python 3.12/3.13 | зелёный |
dsh-bundle в CI: установка, слой, 13 скиллов, disabled-строки, remove |
зелёный |
Что вошло в релиз
Добавлено
- Бандл DeepSeek Harness в
dsh/. 13 Agent Skills доступны сразу; две
MCP-строки (compare-mcpиmarketplace-mcp) выключены по умолчанию и
включаются переменнойRU_MARKETPLACE_MCP_DIRс путём к клону.
RU_MARKETPLACE_MCP_FULLпереключает режим с рекомендуемого сравнения цен
на полный сервер. Установка:
dsh plugin --profile web add github:Vladimir-Human/ru-marketplace-mcp#path:/dsh. - Таргет
dshвmarketplace-mcp install. Печатает готовый
cordis.patch.yml-блок с выключенными строками, а не JSONmcpServers. - Гейты бандла.
test_dsh_bundle.pyсверяет вендор скиллов с источником
и доказывает, что ловит дрейф; новая работа CI ставит бандл в чистый
профиль и утверждает составdsh.profile.bundles, наличие 13 скиллов,
disabled-состояние обеих строк и откат черезremove.
Изменено
- Выходные схемы сжаты до имён полей верхнего уровня. Стоимость
marketplace-mcpупала с ~38 078 до ~13 020 токенов на каждый запрос,
compare-mcp— с ~2 424 до ~918 (измереноscripts/mcp_wire.pyпо
реальному MCP-проводу). Полевые описания остались в## Return Format
описаний инструментов; имена полей по-прежнему закреплены
public_contract.json. - 11
*_selfcheckушли с MCP-поверхности. Они остались CLI-only и
вызываютсяmarketplace-mcp doctor. Модельный набор: 33 инструмента в
двенадцати серверах, 34 в объединённом. Скиллы больше не предлагают
агенту вызвать канарейки; тест паритета теперь требует, чтобы пункт под
заголовком «Tools available» был настоящим MCP-инструментом. - Описания скиллов в лимите dsh.
mpstats-connector591 → 476,
compare-prices500 → 449 символов.
Исправлено
dsh/package.jsonне попадал в git: общий ignorepackage.jsonсъедал
манифест бандла. Локально всё работало, CI checkout ломался; CI и поймал.
Добавлено исключение!dsh/package.json.scripts/e2e_stdio_check.pyи smoke-работа CI всё ещё ожидали счётчики
поверхности до ухода selfcheck'ов (9/4/…/45 вместо 8/3/…/34). Исправлены.
Осталось непроверенным после v1.5.0
Docker-путь (docker run -i через dsh) не
тестировался — docker на машине разработки отсутствует. uvx из
git-ссылки не проверялся. Установка на полностью чистой машине не
измерялась. Поведение при сломанном парсере изнутри dsh не симулировалось.
v1.4.1
RELEASE NOTES — v1.4.1 (2026-08-08)
Патч по итогам четырёх независимых аудитов, проведённых сразу после v1.4.0.
Контракт инструментов не менялся; офлайн-гейт зелёный; изменения — только в
устойчивости счётчиковых парсеров и в полноте провенансов фикстур.
Гейт выпуска
| Проверка | Результат |
|---|---|
ruff check + ruff format --check |
зелёный |
mypy (host / win32 / darwin) |
86 файлов, 0 ошибок |
pytest -m "not live and not cdp" |
1151 passed, 1 skipped (1152) |
check_no_print.py |
72 файла, записей в stdout нет |
check_versions.py 1.4.1 |
72 места согласованы |
check_test_count.py |
1152 в 7 местах |
check_coverage_floor.py |
78.83 % ≥ 70 % |
e2e_stdio_check.py |
13/13 серверов, все v1.4.1 |
Что вошло в патч
Исправлено
coerce_int/compare._as_countбольше не фабрикуют счётчики. Гард
знаков покрывал только ASCII[-+]; юникодный минус (U+2212) и тире
(U+2013/U+2014) проходили насквозь: «1 000–2 000» → 10002000, «−5» → 5.
Теперь оба возвращаютNone(fail loud), в паритете сcoerce_price.
Целочисленные и float-входы не тронуты. Найдено независимым аудитом
доктрины; 5 красных тестов до фикса.
Добавлено
- Провенансы для 7 живых фикстур (citilink
search_grid, avito
js_items_live, 5 фикстур yandex). Ни одной фикстуры без
.provenance.json(url/дата/sha256/метод съёма/ground truth).
Проверено живо (унаследовано от v1.4.0)
| Источник | Поиск | Карточка | Прочее |
|---|---|---|---|
| Wildberries | ✅ | ✅ | reviews, root basket |
| Ozon | ✅ (CDP) | ✅ (CDP) | reviews, reviews_sort |
| Yandex Market | ✅ | ✅ | — |
| Детский мир | ✅ | ✅ | categories |
| Taobao | ✅ (CDP, логин) | фикстура с живой страницы | — |
| Citilink | ✅ (CDP) | фикстуры с живых страниц | — |
| Lamoda | ✅ (CDP) | GraphQL: transport_down | — |
Не проверено живо (честно)
| Источник | Состояние | Почему |
|---|---|---|
| Avito | анти-бот сегодня (403) | фикстура снята 2026-07-28, парсер на ней проверен |
| DNS-Shop | HTTP 401 на момент выпуска | фикстуры сняты 2026-07-28 через CDP |
| Megamarket | ServicePipe блокирует IP | живой search-ответ снят и зафиксирован |
| MPStats | нужен платный токен | без MPSTATS_MP_AUTH честно отвечает auth_missing |
Известные ограничения выпуска
- Chrome ≥ 151 не отвечает на connect_over_cdp Playwright ≤ 1.62 — закрыто
raw-CDP fallback'ом в v1.4.0 (в этом патче не менялось). - Разрыв цен WB search-vs-card неустраним на стороне коннектора (протухание
индекса WB) — защита «цитируй wb_card, сверяй до победителя» в скилле.
v1.4.0
RELEASE NOTES — v1.4.0 (2026-08-08)
Первый тегированный выпуск репозитория. Версия согласована в 72 местах
(scripts/check_versions.py), офлайн-гейт зелёный, живая проверка выполнена на
машине владельца (Chrome 151, логин-сессии для taobao/ozon).
Гейт выпуска
| Проверка | Результат |
|---|---|
ruff check + ruff format --check |
зелёный (217 файлов) |
mypy (host / win32 / darwin) |
86 файлов, 0 ошибок |
pytest -m "not live and not cdp" |
1145 passed, 1 skipped (1146) |
check_no_print.py |
72 файла, записей в stdout нет |
check_versions.py 1.4.0 |
72 места согласованы |
check_test_count.py |
1146 в 7 местах |
check_coverage_floor.py |
78.83 % ≥ 70 % |
e2e_stdio_check.py |
13/13 серверов, реальные MCP-сессии, все v1.4.0 |
marketplace-mcp doctor (живой) |
6 healthy, 0 дрейфа кода (детали ниже) |
Проверено живо (doctor + снятия 2026-08-06…08)
| Источник | Поиск | Карточка | Прочее | Как проверено |
|---|---|---|---|---|
| Wildberries | ✅ | ✅ | reviews, root basket | doctor; сверка search-vs-card на 5 товарах (разрыв 1.7–34.3 %, индекс протухает — см. skills/wb-connector); фикстуры search v9 + card v4 |
| Ozon | ✅ (CDP) | ✅ (CDP) | reviews, reviews_sort | doctor; composer-API снят (403 анти-фрода в лоб, 200 из прогретой сессии), фикстура поиска + провенанс |
| Yandex Market | ✅ | ✅ | — | doctor |
| Детский мир | ✅ | ✅ | categories | doctor; фикстуры card + category из живых API-ответов |
| Taobao | ✅ (CDP, логин) | фикстура с живой страницы | — | doctor; живая карточка снята и зафиксирована, 3 дрейфа экстрактора починены |
| Citilink | ✅ (CDP) | фикстуры с живых страниц (циклы 8, 11) | — | doctor |
| Lamoda | ✅ (CDP) | GraphQL: transport_down сегодня | — | doctor; живая выдача снята и зафиксирована (циклы 34, 41) |
Не проверено живо (честно)
| Источник | Состояние | Почему |
|---|---|---|
| Avito | анти-бот сегодня (403 → пустая страница) | live-фикстура выдачи снята 2026-07-28 (js_items_live.json), парсер на ней проверен; живая сессия на момент выпуска блокировала повтор |
| DNS-Shop | HTTP 401 на момент выпуска | фикстуры поиска и карточки сняты 2026-08-06 через CDP и гоняют экстрактор; на момент выпуска сайт отвечал 401 этой сессии |
| Megamarket | ServicePipe блокирует IP | живой search-ответ снят через raw CDP и зафиксирован; анти-бот не пускает повтор без residential-IP |
| MPStats | нужен платный токен | без MPSTATS_MP_AUTH инструменты честно отвечают auth_missing |
Для всех четырёх: парсеры исполняются на живых байтах, снятых в этом прогоне
(или ранее с провенансом); selfcheck'и честно сообщают inconclusive/drift, а не
маскируют блокировку под успех.
Ключевые изменения выпуска
Доктрина цены/числа (аудит циклов 24–43): каждый локальный парсер цен/счётчиков
приведён к общей гарантии «никогда 0, никогда отрицательная, никогда не-конечная,
никогда не бросает» и зафиксирован тестами на живых байтах:
_as_price(compare) делегирован вmcp_core.coerce_price; фабрикация цены из
range-строк устранена (доказано на живых строках Ozon)._as_count(compare),ssr._to_number/_to_int(yandex),_kopeck_to_rub(wb),
_posted_at(avito),coerce_rating/parse_retry_after(mcp-core) — закрыты
OverflowError/inf/знаковые дыры.- Карточный экстрактор taobao: title из mainTitle, цена собирается из
symbol/text-спанов, shop/sales скоуплены — 3 живых дрейфа починены. - Фикстуры из живых снятий (с провенансом и sha256): ozon-поиск, taobao-карточка,
lamoda-поиск, wb search/card, detmir card/category, megamarket-поиск. - raw-CDP fallback в mcp-core: Chrome 151 перестал отвечать на
connect_over_cdp-рукопожатие Playwright (проверено на 1.61 и 1.62); транспорт
теперь при таймауте attach'а ведёт вкладку по сырому CDP. Поведение на старых
Chrome не изменено (Playwright-путь байт-в-байт прежний). Это вернуло к работе
все CDP-источники на свежем Chrome: doctor показывает 6 healthy вместо 3. - Все 45 инструментов несут секции
## Return Formatи## Error Format,
видимые в живом маунте (griffe обрезает описание послеArgs:— секции
перенесены перед ним); структурный тест пинит наличие секций. - WB-скилл переписан под живое измерение разрыва search-vs-card.
Известные ограничения выпуска
- Playwright ≤ 1.62 не завершает attach с Chrome 151 — закрыт raw-CDP fallback'ом
в этом выпуске; при появлении совместимой версии Playwright fallback просто не
сработает (поведение не изменится). - Avito/DNS/Megamarket блокируют текущую сессию/сеть — снятые фикстуры фиксируют
парсеры, живые selfcheck'и сообщают честные вердикты. - MPStats требует платного токена — опциональный источник.
- Разрыв цен WB search-vs-card неустраним на стороне коннектора (протухание
индекса WB) — защита «цитируй wb_card, сверяй до победителя» встроена в скилл.
Бюджет живых запросов
Потрачено 25/40 рабочих запросов (см. .agent/LIVE-BUDGET.md): citilink 3/8,
dns-shop 3/8, wildberries 3/8, ozon 6/8, lamoda 2/8, taobao 3, detmir 2/8,
megamarket 3/8.
v1.3.1
v1.3.1 — надёжность экстракторов
Патч к v1.3.0.
Инструменты не менялись: имена, аргументы и формы ответов те же, что и раньше.
Что исправлено
Независимый аудит прошёлся по DOM-экстракторам и тестам, которые их держали.
Нашлось четыре расхождения между кодом и тем, что он заявлял.
- Экстракторы читали вывод не тем кодированием. Страница с кириллицей могла
быть декодирована в cp1251, а тест-раннер jsdom терял не-ASCII-символы в
фикстурах. Вывод теперь декодируется как явный UTF-8. - Глифы валют расходились в трёх местах. Регулярные выражения для знаков
рубля, юаня и доллара жили отдельными копиями и разъехались. Теперь они
генерируются из одного списка, и юани признаются наравне с рублём. - DNS показывал наличие из сломанного места. Признак наличия на карточке
брался из соседнего элемента, который может молчать. Теперь — из
textContentкарточки. - Taobao держал две копии логики. Поиск и карточка дублировали разбор DOM.
Оба переведены на общие хелперыmcp-core.
Тесты
Так как поведение раньше проверялось только на живой странице (а она
дрейфует), экстракторы закреплены за снятыми фикстурами:
- Lamoda — поиск
- Citilink — карточка
- Taobao — поиск и карточка
- DNS — карточка
Десять новых DOM-тестов, всего офлайн-набор — 980 (проверено pytest -m "not live and not cdp").
Насколько это проверено
| Слой | Статус |
|---|---|
| Офлайн-тесты (980) | ✅ прогоняются локально и в CI на 3 ОС × 2 Python |
ruff, mypy (host/win32/darwin), check_no_print, check_versions (72 места), check_test_count |
✅ |
| Метаданные и сборка | ✅ binary build test publish в CI |
| Живые страницы |
Число офлайн-тестов и версии в документации приведены к измеренным; расхождений
нет (check_test_count — 980 в 7 местах, check_versions — 1.3.1 в 72 местах).
v1.3.0
v1.3.0 — MPStats и разбор аудита
Двенадцатый источник и довольно много починенного под капотом. Инструменты
прежних одиннадцати работают как работали: имена, аргументы и формы ответов
остались теми же.
Новое: аналитика MPStats
mpstats-mcp стал тринадцатым сервером и первым источником, которому нужен
ключ. Он отдаёт то, чего нет ни у одной витрины: как двигались заказы, цена и
остаток за последние тридцать дней, и как остаток разложен по складам FBS и FBO.
Первоначальный код прислал @Xpos587 в
PR #5. Работа
добросовестная: инварианты рантайма воспроизведены всерьёз.
| Инструмент | Что делает |
|---|---|
mpstats_item(skus, place, oz_fbs) |
Заказы, цены, остатки за 30 дней по сотне SKU разом |
mpstats_warehouses(skus, place) |
Остатки по складам, отдельно FBS и FBO |
mpstats_selfcheck() |
Канарейка: success / drift_detected / inconclusive |
Читайте это до того, как включать. MPStats — единственное место, где проект
входит в аккаунтную зону чужого сервиса. Рискуете там вы, не проект.
Нужен MPSTATS_MP_AUTH, то есть JWT вашей сессии из браузерного плагина. Оферта
MPStats называет основанием для блокировки аккаунта работу с двух и более IP или
браузеров одновременно (п. 5.1.3–5.1.4). Плагин в вашем браузере вместе с
запущенным сервером — это ровно оно. Вторым основанием идёт темп чаще одного
запроса в пять секунд (п. 5.1.1), поэтому MPSTATS_MIN_GAP по умолчанию равен
пяти секундам, а после отказа сервер удлиняет паузу сам. Уменьшать этот интервал
значит приближать блокировку, при которой оплата не возвращается (п. 5.2).
У сервиса есть официальный API с документацией и своим токеном. Если аналитика
нужна вам постоянно, он безопаснее.
Без переменной сервер поднимается штатно и отвечает auth_missing, а остальные
одиннадцать источников работают как прежде. Это единственный ключ во всём
проекте.
Что чинилось
Перед выпуском проект прошёл несколько кругов проверки. Нашлось многое, и не
только в новом коннекторе.
Скидочный бейдж мог стать ценой. coerce_price обещала «никогда не
отрицательное» и выполняла обещание для чисел, но не для строк. Запись -500 ₽
разбиралась в 500, потому что токенайзер смотрел на цифры и пропускал знак перед
ними. Витрины печатают такой бейдж вплотную к настоящей цене, и меньшее число
выигрывало сравнение «где дешевле». Касалось это всех коннекторов, не одного
нового.
Колесо из релиза тянуло чужой код. Зависимость mcp-core не была запинена,
а это имя на PyPI занято посторонним проектом. Человек, скачавший колесо со
страницы релиза и поставивший его через pip, тихо получал чужой пакет, после
чего сервер падал на импорте. Теперь везде стоит mcp-core==1.3.0, и тихая
подмена превратилась в громкий отказ.
Токен попадал в текст ошибки. В лог он писался вычищенным, а в ответ
инструмента уходил как есть. Достаточно перевода строки в конце скопированного
значения, чтобы весь заголовок Cookie оказался в тексте исключения. Теперь
вычищаются оба канала, а сам токен хранится как SecretStr и потому не
печатается даже в дампе настроек.
NaN и бесконечность роняли инструмент целиком. json.loads принимает оба,
а int() на них падает, и одна испорченная ячейка обрывала весь вызов вместо
того, чтобы обнулить одно поле.
Отказ авторизации кешировался. Запись в кеш шла по HTTP 200 до разбора
внутреннего кода, а MPStats отдаёт и отказ, и свои сбои под двухсотым статусом.
Пользователь вставлял свежую cookie и продолжал получать auth_missing, пока
не истечёт время жизни кеша.
Плюс мелочи, каждая из которых жила молча: classify_http_error падала при
любом вызове, импортируя несуществующий модуль; шаг CI с надписью «все серверы
зарегистрировали инструменты» проверял одиннадцать из двенадцати; pre-commit
был красным на чистом дереве, а uv run pre-commit install из CONTRIBUTING.md
не работал вовсе, потому что самого pre-commit не было в зависимостях.
Чтобы это не повторялось
Почти каждая находка была одного вида: утверждение, которое ничто не сверяло с
кодом. Поэтому добавились две проверки, и обе стоят в гейте, в CI и в
pre-commit.
scripts/check_versions.py сверяет версию во всех семидесяти двух местах, где
она записана, включая пин mcp-core. Этот пин видит только тот, кто ставит
колесо: внутри рабочего пространства он молчит, потому что uv.sources
перекрывает ограничение.
scripts/check_test_count.py сверяет число тестов, которое обещает README, с
тем, что реально отбирает коллекция. Оно разъезжалось трижды, причём дважды
прямо во время починки: чинящий добавлял тесты и сам же делал цифру неверной.
Проверенность источников
Не изменилась, и таблица из прошлых заметок остаётся в силе:
| Сверено с живым сайтом | Поставляется, сверка за вами |
|---|---|
| Детский мир, Яндекс Маркет, Авито, карточка Wildberries | Ozon, Мегамаркет, Lamoda, Taobao, DNS, Ситилинк, MPStats |
Все семь отказывают датацентровым адресам либо требуют аккаунта, поэтому
проверить их можно только со своей машины. Известное расхождение цены между
поиском и карточкой Wildberries тоже в силе: для точного числа берите wb_card.
MPStats в правой колонке не случайно. Живых ответов с валидным токеном никто из
проверявших не видел, потому что аккаунта ни у кого не было. Проверен только
неавторизованный контракт, и он совпал с заявленным дословно.
Цифры
948 офлайн-тестов (939 проходят без необязательного jsdom, девять скипаются),
покрытие 77.84%, 45 инструментов в объединённом сервере, тринадцать серверов
отвечают по настоящему MCP-рукопожатию.
Обновление
git pull
uv sync --all-packagesv1.3.0 — MPStats, and an audit's worth of fixes (English)
A twelfth source, and a fair amount repaired underneath. The other eleven
connectors are unchanged: same tool names, arguments and response shapes.
New: MPStats analytics
mpstats-mcp is the thirteenth server and the first source that needs a
credential. It returns what no storefront exposes: how orders, price and stock
moved over the last thirty days, and how stock splits across FBS and FBO
warehouses. The original code came from
@Xpos587 in
PR #5.
Read this before enabling it. MPStats is the one place this project enters
another service's account area, and the risk lands on you.
It needs MPSTATS_MP_AUTH, the session JWT from the browser plugin. The MPStats
offer makes grounds for blocking an account out of running it from two or more
IPs or browsers at once (clauses 5.1.3–5.1.4 — the plugin in your browser plus a
running server is exactly that) and out of a rate above one request per five
seconds (clause 5.1.1). MPSTATS_MIN_GAP therefore defaults to five seconds and
the server lengthens the gap after a refusal. Lowering it moves you toward a
block, and a blocked account is not refunded (clause 5.2). The service also
publishes an official API, which is the safer route for sustained use.
Without the variable the server starts and answers auth_missing; the other
eleven sources are unaffected. It is the only credential in the project.
Fixed
A discount badge could become a price. coerce_price promised "never
negative" and honoured it for numbers but not for strings: -500 ₽ parsed to
500, because the tokeniser read digits and ignored the sign in front of them.
Storefronts print that badge next to the real price, and the smaller number won
any "cheapest" comparison. This affected every connector, not just the new one.
A wheel from the Release pulled a stranger's code. The mcp-core dependency
was unpinned and the name mcp-core on PyPI belongs to an unrelated project, so
pip install of a release wheel quietly fetched that package and the server died
on import. Pinned to mcp-core==1.3.0 everywhere.
The token reached error text. Redacted on the way to the log, raw on the way
to the tool response. Both are redacted now, and the token is held as a
SecretStr.
NaN and infinity aborted a whole call — json.loads accepts both, int()
raises on either.
An auth failure was cached for the whole TTL, because the cache was written
on HTTP 200 before the inner verdict was read.
Also: classify_http_error raised on every call, importing a module that does
not exist; the CI step announcing "all servers registered their expected tools"
checked eleven of twelve; pre-commit was red on a clean tree.
So it stops happening
Nearly every finding was the same shape — a claim nothing compared against the
code. Two checks now do, in the gate, in CI and in pre-commit:
scripts/check_versions.py covers all seventy-two places a version is written,
including the mcp-core pin that only a wheel installer ever sees, and
scripts/check_test_count.py compares the README's advertised test count with
what collection actually selects.
Unchanged
Which sources are verified against live pages. Detsky Mir, Yandex Market, Avito
and the Wildberries card path were compared by hand; Ozon, Megamarket, Lamoda,
Taobao, DNS, Citilink and now MPStats refuse datacenter addresses or require an
account, and stay unverified until you run them yourself. Nobody who reviewed
MPStats had a token, so only its unauthenticated contract is confirmed.
948 offline tests, 77.84% coverage, 45 tools on the unified server, thirteen
servers answering a real MCP handshake.
Автор и мейнтейнер: @Vladimir-Human · MIT
v1.2.1
v1.2.1 — правки по итогам стороннего ревью
Патч к v1.2.0.
Инструменты работают как раньше: их имена, аргументы и формы ответов не менялись.
Обновляться стоит, если вы разработчик и запускали тесты по инструкции из README.
Проект посмотрел человек со стороны, ничего о нём заранее не зная. Нашёл шесть
расхождений, все настоящие.
Что чинилось
npm install jsdom не работал так, как написано в README. Часть тестов
прогоняет настоящий JS-экстрактор по разметке, снятой с живого сайта. Проверка
наличия jsdom искала модуль из корня репозитория и находила его, а сам прогон
шёл из временного каталога, где Node его уже не видел. В итоге девять тестов
падали там, где должны были честно скипнуться. Теперь путь к найденному модулю
передаётся прогону явно.
Число офлайн-тестов в документации разошлось на три значения. README писал
812, docs/ARCHITECTURE.md — 726, отчёт аудита содержал и 822, и 726. Верное
число 822 теперь стоит везде. Для проекта, который в CHANGELOG обещает «числа
приведены к измеренным», расхождение било по самому больному месту.
Быстрый старт запускал живые тесты. В README стояло uv run pytest -q с
подписью «сеть не нужна», а эта команда собирает четыре теста, которым сеть
нужна: у любого пользователя за пределами России первое знакомство с проектом
начиналось с красного теста Wildberries. В примере теперь тот же фильтр, что
использует CI.
Шаг чек-листа проверял пустоту. pytest -m "cdp" собирает ноль тестов:
маркер объявлен, но не стоит ни на одном тесте. Рядом было написано, что это
единственное место, где проверяются источники, требующие браузера. Зелёный
результат такой команды не значил ничего. Команда убрана, вместо неё сказано,
чем этот ярус проверяется на самом деле.
Счёт заменяемых записей конфига был неверным в двух вариантах — «десять» в
одном файле и «двенадцать» в другом. Верное число одиннадцать: серверов
двенадцать, и объединённый монтирует остальные одиннадцать.
Абзац в CHANGELOG дублировался в русской и английской секциях.
Что нашлось уже в самом патче
Готовый патч проверил ещё один читатель, и нашёл в нём три дефекта.
Версия не доехала до __version__. Патч поднимал 1.2.0 → 1.2.1 в
четырнадцати pyproject.toml, но не в тринадцати __init__.py. Установленный
пакет с метаданными 1.2.1 сообщал бы __version__ == "1.2.0" — в релизе, весь
смысл которого в том, что числа сходятся. Причина простая: версия написана в
пятидесяти пяти местах, правится руками, и не сверял её никто.
Теперь сверяет scripts/check_versions.py. Корневой pyproject.toml —
эталон, всё остальное обязано совпасть; проверка стоит в гейте, в CI и в
pre-commit. Первым, что она нашла, был этот самый промах.
Команда mypy из документации не работает на Windows. uv run mypy packages/*/src полагается на то, что звёздочку раскроет оболочка. Bash
раскрывает, PowerShell для нативных команд — нет, и mypy получает путь со
звёздочкой буквально. В CI на Linux зелено, у владельца на Windows красно.
Список файлов переехал в [tool.mypy], команда стала просто uv run mypy.
npm install jsdom оставляет два файла, кроме node_modules:
package.json и package-lock.json. Проект не Node-овский, в репозиторий им
не надо — добавлены в .gitignore.
Что изменилось в текстах
README получил раздел «Как это сделано»: код и документация писались с
ИИ-ассистентами, и там же перечислено, чем это проверено. Вопрос происхождения
текста всё равно виден в истории коммитов, и умалчивать о нём смысла нет.
Отчёт аудита прошёл редакторскую правку. Плотность тире упала с одного на 45
слов до одного на 180, повторяющаяся фигура «X, а не Y» убрана почти везде.
Числа, идентификаторы и вердикты остались нетронутыми.
Что не изменилось
Проверенность источников прежняя, и таблица из заметок к v1.2.0 остаётся в силе:
| Сверено с живым сайтом | Поставляется, сверка за вами |
|---|---|
| Детский мир, Яндекс Маркет, Авито, карточка Wildberries | Ozon, Мегамаркет, Lamoda, Taobao, DNS, Ситилинк |
Все шесть отказывают датацентровым адресам, поэтому проверить их можно только
с домашнего IP и своего залогиненного Chrome. Известное расхождение цены
между поиском и карточкой Wildberries тоже в силе: для точного числа берите
wb_card.
Обновление
git pull
uv sync --all-packagesv1.2.1 — fixes from an outside review (English)
A patch on top of v1.2.0. Tool names, arguments and response shapes are
unchanged; upgrade if you are a developer who ran the tests as the README
described them.
Someone reviewed the project cold and found six real discrepancies.
npm install jsdom did not work as documented. The jsdom probe resolved the
module from the repository root, while the runner executed from a temp directory
where Node could not see it, so nine tests failed instead of skipping. The
resolved path is now passed to the runner.
The offline test count disagreed with itself: 812 in the README, 726 in
docs/ARCHITECTURE.md, both 822 and 726 in the audit report. The measured figure
is 822 and now appears everywhere.
The quickstart ran live tests. uv run pytest -q, labelled "no network
needed", collects four tests that need one, so a first run outside Russia began
with a failing Wildberries test. The example now uses the CI filter.
A checklist step verified an empty set. pytest -m "cdp" collects zero
tests, and the text beside it claimed this was the only place browser-dependent
sources get checked. The command is gone.
A second reader then reviewed the patch itself and found three more. The
version bump reached fourteen pyproject.toml files but not thirteen
__version__ strings, so an installed 1.2.1 wheel would have reported 1.2.0 —
in the release whose whole point is that the numbers agree. A version string
lives in fifty-five places here and nothing was comparing them;
scripts/check_versions.py now does, in the gate, in CI and in pre-commit, and
it found this one. The documented uv run mypy packages/*/src also relies on
the shell expanding the glob, which PowerShell does not do for native commands:
the file list moved into [tool.mypy] and the command is now plain uv run mypy. And npm install jsdom leaves a package.json and a
package-lock.json behind, both now ignored.
Also: the unified server replaces eleven config entries, not ten or twelve; a
CHANGELOG paragraph was duplicated in both languages; the README now states that
the code and documentation were written with AI assistants and lists what
verifies them; the audit report was copy-edited without touching any number,
identifier or verdict.
Unchanged: which sources are verified against live pages. Detsky Mir, Yandex
Market, Avito and the Wildberries card path were compared by hand; Ozon,
Megamarket, Lamoda, Taobao, DNS and Citilink refuse datacenter addresses and stay
unverified until you run them from your own machine.
Автор и мейнтейнер: @Vladimir-Human · MIT
v1.2.0
v1.2.0 — шесть новых маркетплейсов и один общий сервер
MCP-серверы для чтения российских маркетплейсов. Только чтение: ни ключей, ни
паролей, ни аккаунта.
Было пять источников и 22 инструмента. Стало одиннадцать источников и 42
инструмента, и всё это теперь можно подключить одной строкой конфига вместо
одиннадцати.
Что нового
Шесть площадок:
- Авито (4) — объявления, карточки, репутация продавца. Это классифайд, а не
каталог: отзывов на товар не существует, поэтому сигналом доверия служит
рейтинг продавца. - Мегамаркет (3) — поиск и карточки через мобильный JSON API.
- Lamoda (3) — карточки с размерами через GraphQL, поиск через браузер.
- DNS (3) и Ситилинк (3) — электроника.
- Taobao (3) — китайские товары. Цены остаются в юанях и никогда не
конвертируются: зашитый курс молча устареет.
Объединённый сервер marketplace-mcp — одна запись в конфиге MCP-клиента
вместо одиннадцати. Внутри те же 41 инструмент плюс marketplace_sources,
который честно говорит, какие источники поднялись, а какие нет и почему.
Сравнение цен по девяти источникам сразу. Детский мир исключён намеренно:
его API принимает текстовый запрос и игнорирует его, возвращая весь каталог.
Лучше не иметь инструмента, чем иметь такой.
Заметные исправления
Цена могла оказаться платежом по рассрочке. Экстракторы выбирали цену как
наименьшее число на плитке, а плитка DNS показывает «от 5 751 ₽/ мес.» рядом с
ценой 58 999 ₽. Такое значение проходит валидацию и выглядит правдоподобно — то
есть хуже, чем пустое поле. Теперь ценой считается только число, привязанное к
знаку валюты; рассрочка, бонусы, бейдж скидки и счётчик пунктов выдачи
отбрасываются.
Поиск DNS возвращал 24 ссылки без названий и цен. Плитка искалась через
closest(), а он проверяет сам элемент раньше предков — и «карточкой»
становилась ссылка на картинку, у которой нет текста. Страница с товарами
выглядела пустой.
Ситилинк не находил цену никогда. Знак ₽ у него лежит в отдельном элементе
от цифр, а фильтр требовал их в одной строке.
Поиск Авито падал целиком из-за одного поля. location приходит объектом, а
не строкой; Pydantic отклонял ответ, и вместе с одним полем терялась вся
страница объявлений. Там же нашлись ещё два дефекта: у каждого результата поиска
отсутствовала ссылка (ключ urlPath, а искали uriPath — одна буква) и дата
публикации.
Поиск Wildberries за последней страницей отдавал первую. page=20 возвращала
те же 100 товаров, что и page=1 — HTTP 200, без ошибки и без признака. Тот, кто
листает выдачу, собирал дубли и считал, что пагинирует.
Что стоит знать перед использованием
Семь источников требуют вашего Chrome. Ozon, Авито, Taobao, Мегамаркет,
Lamoda, DNS и Ситилинк отказывают датацентровым адресам: капча, редирект-петля
или JavaScript proof-of-work. Они читаются внутри браузера, в который вы вошли
сами. Инструкция — в docs/CDP_SETUP.md.
Три источника работают без всего — Wildberries, Яндекс Маркет, Детский мир.
Успешный selfcheck не означает, что данные верны. Он говорит только, что
транспорт ответил и парсер вернул форму. У DNS был ровно такой случай: после
одной правки selfcheck позеленел, а все названия и цены остались пустыми. Если
число важно — откройте страницу и сравните.
У Яндекса цена на странице — это цена с Плюсом. Инструмент отдаёт обычную
цену в price_rub, подписочную — в price_with_plus. Сравнение цен ранжирует по
обычной.
Насколько это проверено
Честно, потому что проверить может каждый.
| Проверено сверкой с живым сайтом | Поставляется, сверка за вами |
|---|---|
| Детский мир, Яндекс Маркет, Авито, карточка Wildberries | Ozon, Мегамаркет, Lamoda, Taobao, DNS, Ситилинк |
Для четырёх источников название, цена, продавец и наличие сверены с живыми
страницами и совпали. Остальные шесть отвечают отказом на датацентровый адрес,
поэтому проверить их можно только с вашей машины — прогоните *_selfcheck и
сверьте два-три товара глазами.
Парсеры DNS и Ситилинка отдельно проверены на снятой с живых страниц разметке:
тесты прогоняют настоящий JS коннектора и сверяют результат с ценами, которые в
тот момент были на сайте. Не хватает только сквозного прогона через ваш Chrome.
Известное расхождение: поиск Wildberries отдаёт цену примерно на полпроцента
выше карточки, а карточка совпадает с сайтом до рубля. Измерено на одном товаре —
WB режет частые запросы к поиску. Для точной цены берите wb_card.
Совместимость
Имена, сигнатуры и формы ответов 22 инструментов v1.1.0 не менялись. Все
изменения — добавления.
Установка
git clone https://github.com/Vladimir-Human/ru-marketplace-mcp
cd ru-marketplace-mcp
uv sync --all-packagesОдин сервер вместо одиннадцати:
{
"mcpServers": {
"marketplace": {
"command": "uv",
"args": ["run", "--directory", "/путь/к/ru-marketplace-mcp", "marketplace-mcp"]
}
}
}Полная инструкция и таблица инструментов — в README.md, история
изменений — в CHANGELOG.md, отчёт аудита — в
AUDIT_REPORT.md.
v1.2.0 — six new marketplaces and one unified server (English)
MCP servers for reading Russian marketplaces. Read-only: no keys, no passwords,
no account.
Five sources and 22 tools became eleven sources and 42 tools, and the whole
set now connects through a single config entry instead of eleven.
New
Six marketplaces: Avito (4 tools), Megamarket (3), Lamoda (3), DNS (3),
Citilink (3), Taobao (3). Taobao prices stay in yuan and are never converted — a
hardcoded rate would go stale silently.
A unified marketplace-mcp server — one MCP client entry instead of eleven,
carrying the same 41 tools plus marketplace_sources, which reports which
sources actually mounted and why the others did not.
Price comparison across nine sources. Detsky Mir is deliberately excluded:
its API accepts a text query and ignores it, returning the whole catalogue.
Notable fixes
A price could turn out to be an instalment. Extractors picked the smallest
number on a tile, and a DNS tile shows "от 5 751 ₽/ мес." next to a 58 999 ₽
price. That value validates and looks plausible, which makes it worse than an
empty field. A price is now only a number attached to a currency glyph.
DNS search returned 24 links with no titles and no prices — tiles were
resolved with closest(), which tests the element itself first and landed on the
image link, which has no text.
Citilink never found a price — it renders the ₽ glyph in a separate element
from the digits.
Avito search failed entirely over one field — location arrives as an object,
not a string. Two more defects surfaced with it: every search hit was missing its
URL and its publication date.
Wildberries search served page 1 again past the end of the result set —
page=20 returned the same 100 products as page=1, HTTP 200, no marker.
Before you rely on it
Seven sources need your own logged-in Chrome (Ozon, Avito, Taobao, Megamarket,
Lamoda, DNS, Citilink); three work anonymously (Wildberries, Yandex Market,
Detsky Mir). A passing selfcheck does not mean the data is right — it means
the transport answered. On Yandex Market the price shown on the page is the Plus
subscriber price; the everyday price is price_rub.
Verified against live pages: Detsky Mir, Yandex Market, Avito, and the
Wildberries card path. Shipped but unverified from a datacenter address: Ozon,
Megamarket, Lamoda, Taobao, DNS, Citilink — run their *_selfcheck from your own
machine and compare a couple of products by eye.
Known discrepancy: Wildberries search reports a price about half a percent
above the card, and the card matches the site exactly. Measured on one product.
Use wb_card when the number matters.
Compatibility
The 22 tools of v1.1.0 keep their names, signatures and wire shapes. Every change
is additive.
Автор и мейнтейнер: @Vladimir-Human · MIT
v1.1.0
What's Changed
- release: v1.1.0 by @Vladimir-Human in #1
New Contributors
- @Vladimir-Human made their first contribution in #1
Full Changelog: v1.0.0...v1.1.0
v1.0.0 — first public release
MCP servers for reading Russian marketplaces: Wildberries, Ozon, Yandex Market,
Detsky Mir, plus cross-marketplace price comparison.
Read-only. No credentials, no API keys, no account required.
What is in it
20 tools across 5 stdio MCP servers sharing one runtime:
- Wildberries (7) — search, cards, reviews, seller legal identity (INN/OGRN), catalog tree
- Yandex Market (3) — multi-seller prices, per-star rating distribution, reviews
- Detsky Mir (4) — kids goods, offline store stock, category listings
- Ozon (4) — search, cards, reviews via TLS impersonation with a browser fallback
- Compare (2) — "where is this cheapest?" across every installed marketplace at once
Highlights
- Two prices, never conflated. Yandex Market advertises a subscriber price 25-30%
below its everyday one; both are reported separately and only the everyday price is ranked. - Partial results are labelled as such. Marketplaces fail independently; each
reports its own outcome, andcomplete: falsesays so plainly. - Seller identity.
wb_sellerreturns the registered legal entity and tax ids
behind a storefront name. - No credentials anywhere. The authenticated Ozon tier runs inside a Chrome you
logged into yourself.
Notable fix
wb_search returned pages where almost nothing had a price: it resolved ids through a
stale index that served delisted SKUs. It now reads search.wb.ru v9 directly — one
request instead of two, 100 priced results per page instead of 30 mostly-empty ones.
Quality
219 offline tests; ruff, ruff-format and mypy clean; CI on Ubuntu/Windows/macOS
against Python 3.12 and 3.13.
Full notes: CHANGELOG.md - Feasibility findings for four marketplaces
that did not make the release: docs/ANTI_BOT.md