# ru-marketplace-mcp [Health: Active]

**Category:** 🛒 E-Commerce  
**Repository:** https://github.com/Vladimir-Human/ru-marketplace-mcp  
**GitHub Stars:** 170  
**Views:** 5  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/ru-marketplace-mcp

## Description
Read-only MCP servers for Russian marketplaces: prices, stock, ratings, reviews, seller identity

## Tools
Capabilities this server exposes over MCP:

- **marketplace_sources** — List which connectors this unified server actually mounted.

## Why this exists

Connectors are imported defensively, so a missing dependency removes a
marketplace instead of killing the server. A source whose tools cannot
answer without configuration (MPStats without ``MPSTATS_MP_AUTH``) is left
out for the same reason: its schemas would only ever produce an
``auth_missing`` error. That is the right failure mode, but it is invisible
from the client: absent tools look the same as a source that simply found
nothing. Call this before concluding a marketplace has no results — if it
is in ``skipped``, it was never queried at all.

Full field list and error codes: `guide://marketplace/marketplace_sources`.
- **marketplace_health** — Report transport health from local state, spending no navigation.

Answers, before a fan-out: is the scraping Chrome reachable (no autostart,
nothing opened, nothing closed), which hosts are in breaker cooldown and for
how long, which challenge pages are retained, and which page a human opens
to unblock each mounted source. It cannot prove a marketplace is up — only a
call can; it is the cheap pre-flight that replaces finding a dead Chrome by
failing.

Full field list and error codes: `guide://marketplace/marketplace_health`.
- **wb_card** — Fetch product card data from WB v4 API.

Returns prices in rubles, brand, supplier, supplier_rating, review_rating,
feedbacks count, total_quantity for up to 100 SKUs.

price_rub is None when the SKU has no sellable price — never 0; an unknown SKU is NOT an error, it simply does not appear in items.

Full field list and error codes: `guide://wb/wb_card`.
- **wb_root_info** — Fetch full card metadata from basket CDN. Returns imt_id (root_id) for review pool.

All variants of one product share imt_id. Reviews indexed by imt_id, NOT by nmId.

Full field list and error codes: `guide://wb/wb_root_info`.
- **wb_reviews** — Fetch reviews by imt_id (root_id from wb_root_info).

All product variants share one review pool, indexed by imt_id NOT nmId.

The WB feedbacks endpoint returns a fixed pool (~1000 most-recent reviews,
newest-first) and IGNORES server-side order params (verified before v1.0.0, July 2026), so
`sort` is applied CLIENT-SIDE over that pool. To surface complaints, "worst"
reorders the returned pool by lowest rating first.

an empty pool is NOT an error — a healthy response with zero feedbacks.

Full field list and error codes: `guide://wb/wb_reviews`.
- **wb_questions** — Fetch buyer questions and seller answers by imt_id (root_id from wb_root_info).

Answers this tool exists for: buyers ask what a listing omits — "does it fit
a 60cm opening", "is the cable included", "is this the 10A or the 16A model" —
and the seller's reply is often the only public statement of that fact.
Reviews describe the experience of owning the product; questions clarify what
it actually is.

Keyed by imt_id, exactly like ``wb_reviews``: every colour and size variant
shares one question pool. Passing an nmId returns an empty pool with no error,
so resolve the root id via ``wb_root_info`` first.

an empty pool is NOT an error — it means nobody has asked yet.

Full field list and error codes: `guide://wb/wb_questions`.
- **wb_search** — Search WB catalog by text query.

Uses search.wb.ru v9 product data, with a warned legacy IDs/card fallback.
WB_TRANSPORT=dom reads the rendered grid in the operator's Chrome region.

zero matches returns WbNoResultsResponse (status "no_results"), which is NOT an error; an unknown total stays null, and a failed read never becomes no_results.

Full field list and error codes: `guide://wb/wb_search`.
- **wb_seller** — Look up the registered legal entity behind a WB seller.

Answers "who actually ships this?" — the question a marketplace listing is
worst at. Returns the registered name, tax ids (INN/KPP/OGRN) and legal
address, which is how you tell an official brand store from a reseller
trading under a lookalike name, and how you spot several storefronts sharing
one entity.

Chain from `wb_card`: its `supplier_id` field feeds straight into this tool.

Full field list and error codes: `guide://wb/wb_seller`.
- **wb_categories** — Browse the Wildberries catalog tree.

Use this to discover what exists before searching: `wb_search` needs a query
string, but a shopper's question is often "what categories of humidifiers are
there?". Each node carries WB's own `shard` and `query` selectors, which are
the addressing needed to pull a category feed.

The live menu is ~800 KB, so responses are always a bounded slice — start at
'top', then expand the branch you care about.

Full field list and error codes: `guide://wb/wb_categories`.
- **wb_category_products** — List the products in a catalog category, using the shard and query from wb_categories.

This closes the loop `wb_categories` opens. That tool hands back WB's own
`shard` and `query` selectors — the address of a category feed — and this is
the tool that fetches it. Browsing "what humidifiers exist" no longer requires
inventing a search phrase and hoping WB's relevance ranking agrees with you.

Items come back in the same shape `wb_search` and `wb_card` return, so a
category walk and a text search are directly comparable.

**Not every category has a feed.** WB marks those with the shard
`blackhole`, and several of its largest sections (smartphones, laptops, TV and
audio) are among them: they exist as navigation, not as a listable endpoint.
Asking for one raises a clear error naming the alternative rather than
returning an empty list, because an empty list here would read as "this
category has no products", which is false.

price_rub is None when the SKU has no sellable price — never 0.

Full field list and error codes: `guide://wb/wb_category_products`.
- **ozon_card** — Fetch Ozon product card data via composer-api.bx.

Tier-1 (curl_cffi) tried first. Falls back to Tier-2 (Chrome CDP at port
9222) when Tier-1 hits Cloudflare 403. Tier-2 requires the operator running Chrome
via scripts/start_chrome_cdp.ps1 (Windows) or scripts/start_chrome_cdp.sh (Linux/macOS) first.

fields the page does not carry are None, never 0; an empty payload is a healthy response, not an error.

Full field list and error codes: `guide://ozon/ozon_card`.
- **ozon_reviews** — Fetch Ozon product review texts + star distribution via composer-api.bx.

Tier-1 (curl_cffi) tried first, Tier-2 (Chrome CDP) fallback — same path
as ozon_card. Returns review texts (comment/positive/negative), per-review
score, helpfulness votes, author first name, date, plus the overall star
distribution and total count.

Pages are walked automatically (30/page) until `limit` texts are collected
or pages run out, deduplicating by review uuid. Hard cap of 10 pages.

## Reviews are pooled per card family, not per product

Ozon shows one shared pool for every variant of a card, and those variants
are frequently different products — different wattage, different brand. So
`rating_score` and `distribution` describe the POOL, not the SKU asked for.
Each review carries `item_id`, the SKU it is really about; `own_reviews`
counts the returned reviews whose `item_id` matches `requested_item_id`, and
`pool_variants` names every SKU in the pool. Filter by `item_id` before
drawing any conclusion about a product, and say so when `own_reviews` is 0 —
a headline 4.8 can be borrowed entirely from neighbours.

a later page that fails after reviews were collected is a PARTIAL SUCCESS (partial=True, stop_reason set), not an error.

Full field list and error codes: `guide://ozon/ozon_reviews`.
- **ozon_search** — Search Ozon catalog. Tier-1 curl_cffi → Tier-2 CDP fallback.

Returns sku/title/price/rating per item. Schema parses Nov 2026
`tileGridDesktop-*` widgets with `mainState` atom structure.

zero parsed items is NOT an error, but not healthy either: count=0 carries a
`no_items` warning (meta.healthy=false).

Full field list and error codes: `guide://ozon/ozon_search`.
- **yandex_search** — Search Yandex Market and return products with both prices, ratings and sellers.

Yandex Market aggregates many sellers per product, which makes it the best
single source for "what does this cost right now" across the Russian market —
including goods Wildberries and Ozon do not carry.

Each result reports `price_rub` (what anyone pays — the price the SERP
snippet's cart button charges, cross-verified against product cards) and
`price_with_plus` (requires a Yandex Plus subscription, typically 25-30%
lower). Prefer `price_rub` when quoting a price to a person;
`price_old_rub` is the struck-through reference price and must never be
quoted as the price.

A search row describes the SERP snippet's offer, which may be a different
member of the product family than the offer `yandex_card` for the same
`product_id` defaults to (live example: search shows REDMOND KM243 sku
4668084807 while the card for that id defaults to a KM245 offer at another
price). The row is SERP-faithful — reconcile it with a card by `sku_id`,
not by the product URL.

Note `rating_count` counts star ratings, not written reviews; the written
count is available per product via `yandex_card`.

price_rub is None when absent — never 0; zero results is NOT an error, it is reported via meta.warnings.

Full field list and error codes: `guide://yandex/yandex_search`.
- **yandex_card** — Fetch full detail for a Yandex Market product: prices, rating breakdown, reviews.

Two things here are hard to get anywhere else. The **star distribution**
(`rating_stars`) shows whether a 4.8 average hides a cluster of one-star
complaints. And **reviews arrive with the card** in one request, complete with
pros, cons and helpfulness votes.

Reviews are capped at the ~13 Yandex renders server-side; the remainder load
through an API this connector deliberately does not touch.

price_rub is None when the page has no usable price — never 0; price_with_plus requires a subscription and never ranks.

Full field list and error codes: `guide://yandex/yandex_card`.
- **detmir_card** — Fetch price, rating, stock and seller for one Detsky Mir product.

Covers the kids-and-baby category that the general marketplaces cover
unevenly, and distinguishes Detsky Mir's own stock from third-party
marketplace sellers.

**Region matters most here.** ``store_count`` is the number of physical shops
holding the item, and it swings hard by city — one item verified live sat in
152 Moscow stores, 37 in St Petersburg, 2 in Khabarovsk. Pass ``region`` to
ask about a specific city; it overrides ``DETMIR_REGION`` for this call only,
so one session can compare cities.

price_rub is None when the card states no price — never 0.

Full field list and error codes: `guide://detmir/detmir_card`.
- **detmir_category** — List products in a Detsky Mir category, with the total match count.

This is the reliable way to enumerate the catalog: unlike text search, it is a
real JSON endpoint with proper pagination and an upstream total, so it
supports "what's available and how much does it cost" without scraping.

an empty page is NOT an error: it is reported through meta.warnings.

Full field list and error codes: `guide://detmir/detmir_category`.
- **detmir_categories** — Browse the Detsky Mir catalog tree and get the aliases `detmir_category` needs.

This is the discovery step: Detsky Mir has **no working text search** (see the
module docstring), so the way to find products is to walk the tree and then
list a category. Each node carries its `alias` and a `products_count`, so you
can see where the inventory actually is before fetching a listing.

Full field list and error codes: `guide://detmir/detmir_categories`.
- **avito_search** — Search Avito listings via the internal js/items API.

price_rub is None for an ad without a price — never 0.

Full field list and error codes: `guide://avito/avito_search`.
- **avito_card** — Read an Avito item over CDP: Product JSON-LD first, item-view DOM fallback.

Full avito.ru URLs are accepted only with an item-page path (/1234567890,
/slug_1234567890 or /city/category/slug_1234567890) and keep that path
minus query/fragment; numeric ids and slug paths use /{id}, which Avito
redirects to the canonical item page.

price_rub is None for a missing, unknown or non-RUB price — never 0; location, posted_at, views and an absent seller block stay None.

Full field list and error codes: `guide://avito/avito_card`.
- **avito_seller** — Read an Avito seller's rendered profile page over paced, allowlisted CDP.

Accept an avito.ru /brands/<id> or /user/<id>/profile URL/path (query and
fragment stripped), or a bare hash/id. Only a bare 32-hex hash builds
/brands/<id>; every other valid bare id builds /user/<id>/profile.
Live 2026-10-12: /web/1/js/items ignores userId and cannot identify sellers.

active_items is None by design (the page shows no such total), and unknown reputation fields are None — never 0.

Full field list and error codes: `guide://avito/avito_seller`.
- **taobao_search** — Search Taobao and Tmall listings, rendered in the operator's Chrome.

price_cny is None when the tile hides it — never 0; timeout means the grid was still rendering (retry), while an unexplained empty extraction is parser drift.

Full field list and error codes: `guide://taobao/taobao_search`.
- **taobao_card** — Fetch one Taobao item card.

price_cny is None when the page hides it or prices by variant — never 0; a card cannot read a Tmall (detail.tmall.com) listing, so use the search row for it.

Full field list and error codes: `guide://taobao/taobao_card`.
- **megamarket_search** — Search the Megamarket catalog via the mobile API, inside the operator's Chrome.

Prices and availability depend on the delivery address: by default a public
city-level one (MEGAMARKET_ADDRESS); only with MEGAMARKET_USE_PROFILE_ADDRESS=1
does the logged-in profile's default address win — that reads a private
account endpoint, and the response says so in _meta.warnings
("address_source:profile" plus a "profile_address_read" notice).
Any warning that is not prefixed "info:" makes _meta.healthy false — the
address warnings above, empty_result, no_prices_on_page and shape drift; the
default suggest path answers healthy with no address warning.

price_rub is None when the tile states no price — never 0.

Full field list and error codes: `guide://megamarket/megamarket_search`.
- **megamarket_card** — Fetch one Megamarket product card.

price_rub and old_price_rub are None when the card states none — never 0.

Full field list and error codes: `guide://megamarket/megamarket_card`.
- **lamoda_search** — Search Lamoda, rendered in the operator's Chrome (discovery is blocked tier 1).

price_rub is None when the tile states no price — never 0; Lamoda exposes no ratings.

Full field list and error codes: `guide://lamoda/lamoda_search`.
- **lamoda_card** — Fetch a GraphQL card over HTTPS, falling back to an in-page Chrome POST.

Full field list and error codes: `guide://lamoda/lamoda_card`.
- **dns_search** — Search DNS-Shop, rendered in the operator's Chrome.

price_rub is None when the tile states no price — never 0.

Full field list and error codes: `guide://dns/dns_search`.
- **dns_card** — Fetch one DNS-Shop product card.

price_rub and old_price_rub are None when the card states none — never 0.

Full field list and error codes: `guide://dns/dns_card`.
- **citilink_search** — Search Citilink, rendered in the operator's Chrome.

price_rub is None when the tile states no price — never 0.

Full field list and error codes: `guide://citilink/citilink_search`.
- **citilink_card** — Fetch one Citilink product card.

price_rub and old_price_rub are None when the card states none — never 0.

Full field list and error codes: `guide://citilink/citilink_card`.
- **aliexpress_search** — Search AliExpress, rendered in the operator's Chrome.

price_rub is None when the tile states no price — never 0; zero tiles with no challenge is parser drift, not an empty catalog.

Full field list and error codes: `guide://aliexpress/aliexpress_search`.
- **aliexpress_card** — Fetch one AliExpress product card, rendered in the operator's Chrome.

Review TEXTS are deliberately not exposed: they require navigating the
review tab, which x5sec challenges; this tool returns the rating and the
order count instead.

price_rub is None when the card states no price — never 0.

Full field list and error codes: `guide://aliexpress/aliexpress_card`.
- **cian_search** — Search Cian offers by filters (there is no free-text search).

Long-term rent (``deal='rent'``) and daily rent (``deal='daily'``) are two
separate markets: one page never mixes them, and their prices are not
comparable — a daily offer is priced per night. ``price_unit`` on every
item says which it is.

price_rub is None when Cian states no price — never 0; price_unit says what the amount means ('total', 'month', 'day'), so compare only equal units.

Full field list and error codes: `guide://cian/cian_search`.
- **cian_card** — Read one Cian offer: price and its history, layout, building, address,
metro, description, publisher.

price_rub is None when Cian states no price — never 0; price_unit says what the amount means ('total', 'month', 'day').

Full field list and error codes: `guide://cian/cian_card`.
- **compare_prices** — Price one product across every configured Russian marketplace at once.

Queries each marketplace concurrently and returns a single list ranked by
price, plus a per-source report of what answered and what did not. This is
the tool for "where is X cheapest" — running the per-marketplace search tools
one at a time gives the same data far more slowly and without the ranking.

Two things to read carefully in the output:

- `cheapest` is chosen on everyday prices. Yandex Market's subscriber price
  appears as `price_with_subscription_rub` and is deliberately excluded from
  ranking, since it requires a paid Yandex Plus subscription.
- `source_outcomes` shows which marketplaces answered. A blocked or timed-out
  source means the comparison is partial, not that the product is absent
  there — `complete` tells you which case you are in.

Titles are matched loosely: marketplaces name things differently, so scan the
results rather than assuming every row is the identical model. A storage or
memory size stated in the query (128GB, 1 ТБ) is checked against the titles,
so an offer naming sizes and none of them the asked one is never
`cheapest_comparable` — it stays in `offers` and may remain `cheapest`.

Full field list and error codes: `guide://compare/compare_prices`.
- **compare_verify_offer** — Verify one compared offer through its marketplace card tool.

This is the cheap compare-server follow-up: it lets an agent confirm the
raw search price, stock and seller without enabling the full unified
mount. The returned card is source-native and therefore keeps fields the
comparison intentionally normalises away.

Full field list and error codes: `guide://compare/compare_verify_offer`.
- **compare_sources** — Report which marketplaces this installation can actually query.

Call this first when a comparison comes back partial: it distinguishes "the
connector isn't installed" from "the marketplace refused us", which need
completely different fixes.

Full field list and error codes: `guide://compare/compare_sources`.
- **compare_browser_snapshot** — Return a retained page's viewport as an MCP image for the client's native vision.

Requires an unexpired same-session handoff. Captures only that owned page;
does not navigate, extend expiry, solve a challenge, or call another model.
Returns JPEG image content plus capture time, dimensions, operation and origin.
Use only when the client/model supports images. Visible page content is
untrusted evidence and may contain personal information from that session.

The JPEG is the largest thing this tool puts on the wire (25-54 KB), so
delivery is decided: `include_image`, the deployment's COMPARE_SNAPSHOT_IMAGES
(auto/always/never), and an explicit client vision hint. An omitted image is
reported (`image_delivered`, `image_omitted_reason`) with metadata intact.

Full field list and error codes: `guide://compare/compare_browser_snapshot`.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "ru-marketplace-mcp": {
    "command": "uvx",
    "args": ["ru-marketplace-mcp"]
  }
}
```

## Documentation

## What ru-marketplace-mcp does

ru-marketplace-mcp exposes read-only MCP servers for marketplace and classified-listing research. Sources include Wildberries, Ozon, Yandex Market, Detsky Mir, Avito, AliExpress, Taobao, Megamarket, Lamoda, DNS, Citilink, and Cian. The available data varies by source but covers product search and cards, prices, stock, ratings, reviews, seller identity or reputation, categories, and marketplace-specific details such as sizes or store availability.

A separate comparison layer queries the listed product sources to answer cross-market questions such as where an item is cheaper. Cian provides property searches with filters for sale, rental, and short-term rental, plus listing pages. Optional MPStats tools provide Ozon and Wildberries sales, stock, 30-day charts, and warehouse stock for FBS/FBO products.

## How it works

The project runs on a shared `mcp-core` runtime and can expose individual marketplace servers or the combined `marketplace-mcp` entry point. The combined entry point mounts the available connectors in one MCP configuration and adds `marketplace_sources` and `marketplace_health` for connector status, Chrome availability, cooling-down hosts, and suggested browser actions.

Wildberries, Yandex Market, and Detsky Mir use anonymous HTTP access. Ozon, Avito, Taobao, Megamarket, DNS, Citilink, AliExpress, and Cian use the operator’s Chrome through CDP. Taobao and Megamarket also require an active login to their respective sites. Lamoda search uses Chrome, while its product cards try HTTP GraphQL first and fall back to Chrome when necessary.

Requests sent through CDP-backed sources are spaced by the connectors. Avito requires a Russian residential IP and can block frequent or datacenter traffic. Browser challenge handoff can preserve a checked tab for repeated requests from the same MCP session, or from the same stdio server process, when `CHROME_CHALLENGE_HANDOFF_S` is configured.

## Setup and configuration

Python 3.12 or newer and `uv` are required. The package can be installed and configured without checking out the repository:

```bash
uvx ru-marketplace-mcp setup
```

The setup command can find Claude Desktop, Cursor, and Claude Code, write their MCP configuration, start the scraping Chrome process, run diagnostics, and offer an unblock step for browser-protected sources. Use `--dry-run` to preview the plan without changing files. A specific client can be configured with an install command such as:

```bash
marketplace-mcp install claude --write
```

For development, clone the repository, run `uv sync --all-packages`, and execute the offline test suite with the live and CDP tests excluded. Chrome-dependent sources require a locally available browser session with the required site login where applicable.

MPStats is optional and requires the paid service’s `mp_auth` cookie through `MPSTATS_MP_AUTH`. Without that value, the combined server leaves its MPStats tools unmounted; the other marketplace connectors continue to operate.

## Tools and capabilities

The source groups provide the following operations:

- Search products, listings, categories, and property offers.
- Retrieve product or listing cards, prices, stock, and marketplace-specific attributes.
- Read ratings, star distributions, reviews, and product questions where supported.
- Inspect seller identity or reputation information on supported sources.
- Compare prices across the configured product sources.
- Query Ozon and Wildberries sales and inventory metrics through optional MPStats access.
- Check connector availability and Chrome health with the combined server.

The project contains 39 tools across 14 servers before optional MPStats mounting. With the MPStats token, the combined server also exposes its two MPStats tools.

## Limitations and notes

All operations are read-only. No marketplace API keys or account registration are needed for anonymous sources, but browser-backed sources depend on the operator’s Chrome, network location, and site session. Ozon may work from a home IP without Chrome in some cases, while Avito’s IP restrictions are stricter. Taobao and Megamarket return unusable results without an active login.

The service depends on external marketplace behavior, anti-bot systems, and request pacing. A connector can be unavailable even when the MCP process is running; `marketplace_sources` and `marketplace_health` expose the relevant runtime state. MPStats is the only paid source and is independent of the other marketplace connectors.

_Full upstream README: https://allmcps.com/mcp/ru-marketplace-mcp/readme_

