Подключение MCP
shopmcp.ru предоставляет remote MCP-сервер по адресу:
https://shopmcp.ru/mcp
Транспорт: Streamable HTTP (JSON-RPC 2.0 поверх HTTPS).
Claude Code
claude mcp add --transport http shopmcp https://shopmcp.ru/mcp
Claude Desktop / Cursor
{
"mcpServers": {
"shopmcp": {
"url": "https://shopmcp.ru/mcp"
}
}
}
Модель
Outlet — единая сущность «точка получения товара». Четыре типа:
kind=store— физическая точка, цены per-store (Лента, Магнит). У outlet'а однаOutletLocationс capability-флагамиwalkin/pickup.kind=network— сеть с общим каталогом, цена единая в регионе (Эльдорадо, Леруа). У outlet'а несколько locations (магазины региона).search_productsзовётся ОДИН раз — цены одни во всех locations. Наличие per-магазин — вproduct.stock_by_location[].kind=delivery— курьерская доставка с фиксированной зоной. Поляdelivery_fee,service_fee,min_order,fee_note,min_days/max_days.kind=marketplace— маркетплейс (Ozon/WB и т.п.). Конкретные варианты получения (ПВЗ / постамат / курьер) зависят от товара и приходят вproduct.delivery[].
У каждого outlet'а есть opaque id — строка вида shp_BWxlbnRhbXNrLXR2ZXJza2F5YS0x. LLM получает её в поле id через find_outlets и передаёт её как параметр outlet_id в search_products/get_product. Префиксы: shp_ (store), ntw_ (network), dlv_ (delivery), mkt_ (marketplace).
Инструменты
list_retailers(query?, has_offline?, has_delivery?)
Полный справочник активных торговых сетей. Каждый элемент включает promotions[] — сетевые акции этой сети.
find_outlets(area, retailer_ids?, kinds?)
Точки получения товара в зоне поиска. Возвращает массив outlet'ов разных типов — LLM выбирает подходящий по контексту запроса (имя, описание, pricing, расстояние, capabilities) и забирает id.
area — либо точка с радиусом, либо маршрут:
area = {
type: "radius",
center: { type:"address", text:"..." } | { type:"coords", lat, lon },
radius_m: 1500
}
// или
area = {
type: "along_path",
from: Location, to: Location,
max_extra_m: 800 // насколько outlet может уклоняться от прямой "от-до"
}
Внутри shopmcp комбинирует два источника: статичный индекс физических точек (rstar по координатам — store/network) и live-запрос у парсера (delivery/marketplace). Для kind=network outlet'ов physical locations группируются в один outlet с массивом locations[]. Результаты сортируются по расстоянию, обрезаются до 30.
Фильтры: retailer_ids (UUID'ы), kinds (например ["network"] чтобы получить только сети с общим каталогом).
search_products(outlet_id, queries[], in_stock_only?, extra_fields?)
Поиск товаров в одном pricing-контексте по нескольким текстовым запросам сразу. Удобно для сборки корзины: queries: ["молоко 3.2%", "хлеб бородинский", "сыр гауда"]. Ответ — { outlet_id, results: { "молоко 3.2%": {products, ...}, ... } }. Лимит — 30 товаров на каждый запрос.
По умолчанию листинг содержит минимум: id, title, price, currency, in_stock + promotions. Для kind=network добавляется stock_by_location[], для kind=marketplace — delivery[]. Остальное — через extra_fields: ["image", "rating", "brand", ...].
Контекст outlet'а и retailer'а в ответе не дублируется — LLM помнит из find_outlets. Если потеряла (очистила историю) — вызывает find_outlets повторно по координатам.
get_product(outlet_id, product_id, fields?)
Полная карточка одного товара. По умолчанию все поля что отдал парсер: description, images, categories, attributes, nutrition, rating/votes. fields: [...] ограничивает выдачу. Ответ — { outlet_id, product }.
Долгоживущие ссылки: LLM сохранила (outlet_id, product_id) в чате — через месяц зовёт и получает актуальную цену.
Типы
type Location =
| { type: "address", text: string }
| { type: "coords", lat: number, lon: number }
type Area =
| { type: "radius", center: Location, radius_m: number }
| { type: "along_path", from: Location, to: Location, max_extra_m: number }
type Outlet = {
id: string, // opaque shopmcp-id, передавай как outlet_id в search_products/get_product
kind: "store" | "network" | "delivery" | "marketplace",
retailer_id: string,
name: string,
description?: string,
// Физические точки. store=1, network=N, delivery/marketplace=0.
locations?: OutletLocation[],
// Pricing. Три состояния fee-полей: отсутствует (неизвестно),
// "0" (явно бесплатно), "99.00" (конкретное значение).
delivery_fee?: string, // курьерская доставка (одна цена для любой корзины)
delivery_tiers?: DeliveryTier[], // ступенчатые пороги: «199 от 1000, 0 от 4099»
service_fee?: string, // сервисный сбор платформы
pickup_fee?: string, // сборка готового заказа для самовывоза
min_order?: string,
max_weight_kg?: string, // max вес заказа в кг
fee_note?: string, // динамические условия в свободной форме
min_days?: number, max_days?: number, // срок в целых днях
delivery_time?: string, // или свободный формат: «от 40 минут», «2-3 часа»
promotions?: Promotion[]
}
type OutletLocation = {
id?: string, // нужен для network (привязка stock_by_location)
address?: string, lat?: number, lon?: number,
hours?: string, phone?: string,
walkin?: boolean, pickup?: boolean,
promotions?: Promotion[], // локальные акции этой точки
distance_m?: number // sorted ascending
}
// Ступенчатый тариф доставки. LLM для конкретной корзины берёт последний
// tier у которого cart_sum >= min_order.
type DeliveryTier = {
price: string, // "0" — явно бесплатно
min_order: string
}
// Только для kind=network — наличие/сроки per-магазин (поле product.stock_by_location[]).
type LocationStock = {
location: string, // ссылка на OutletLocation.id
in_stock: boolean,
stock_qty?: string,
min_days?: number, max_days?: number // срок поставки если in_stock=false
}
// Только для kind=marketplace — варианты получения per-SKU (поле product.delivery[]).
type DeliveryVariant = {
type: "pickup" | "partner_pickup" | "courier",
name: string,
description?: string,
delivery_fee?: string, service_fee?: string, fee_note?: string,
min_days?: number, max_days?: number
}
Сценарии
Молоко в магазине у дома
find_outlets({ area: { type:"radius", center:{ type:"coords", lat, lon }, radius_m: 1500 }, kinds:["store"] })- Берём
idближайшего outlet'а (у store однаlocations[0]с адресом). search_products({ outlet_id, queries: ["молоко 3.2%"] })- (Опц.)
get_product(outlet_id, product_id)— полная карточка сdescription/nutrition.
Холодильник в сети с общим каталогом
find_outlets({ area: { type:"radius", center, radius_m: 3000 }, kinds:["network"] })→ одна Эльдорадо с 3locationsв Москве.search_products({ outlet_id, queries: ["холодильник Bosch"] })— ОДИН запрос, цены общие. Парсер вернётstock_by_location[]per-магазин.- LLM сама решает, какую
locationрекомендовать — это компромисс между наличием (stock_by_location),distance_m, часами, capabilities и количеством товаров из корзины, попавших в эту точку. Например, «оливье из 7 продуктов» — лучше один магазин подальше, где все 7 есть, чем ближний, где только 4.
Сравнение цен «магазин рядом vs распред-центр»
find_outlets({ area: { type:"radius", center, radius_m: 1500 }, kinds:["delivery"] })→ 2 варианта.- Сравниваем
fee_note/delivery_fee/min_days/min_order. - Параллельно зовём
search_productsдля каждогоoutlet_id, складываем корзину и сравниваем итог.
По дороге с работы домой
find_outlets({ area: { type:"along_path", from:{type:"address", text:"офис, ул. Льва Толстого 16"}, to:{type:"address", text:"дом, Тверская 1"}, max_extra_m: 500 }, kinds:["store","network"] })- Получаем outlet'ы (физические + network), чьи locations лежат в коридоре до 500 м от прямой маршрута, отсортированные по distance_m.
- Берём ближайший по пути
idи зовёмsearch_products.
Через месяц
LLM из сохранённой переписки берёт outlet_id + product_id, зовёт get_product(outlet_id, product_id) → получает свежую цену.