- Python 96.9%
- Nix 3.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
По умолчанию apteki_search/apteki_where сужают выдачу: - in_stock_only=true — скрыть отсутствующее. У Product добавлен нормализованный флаг in_stock, каждый провайдер проставляет его из своего сигнала (available_quantity, is_in_stock, «в N аптеках» и т.п.). - include_aggregators=false — скрыть агрегаторы (Мегаптека, АСНА, ГорЗдрав, 36,6): их выдача пересекается с одиночными сетями и между собой (ГорЗдрав и 36,6 — почти один бэкенд), охваты «в N аптеках» складывать нельзя. Скрытые перечислены в ответе (hidden_aggregators). Провайдеры помечены aggregator. Ответ apteki_search теперь структурирован по провайдеру (title, aggregator, stock_kind, items), как и apteki_where. Co-Authored-By: Eva |
||
| apteki_mcp | ||
| .gitignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| flake.lock | ||
| flake.nix | ||
| pyproject.toml | ||
| README.md | ||
apteki-mcp
MCP-сервер поиска лекарств по российским интернет-аптекам: что за препарат, по какой цене и — главное — в каких аптеках и по какому адресу его взять.
Инструменты
| Инструмент | Что делает |
|---|---|
apteki_cities |
найти город и его слаг |
apteki_search |
карточки препарата по площадкам: названия, цены, рецептурность |
apteki_where |
аптеки с адресами, координатами и часами работы, ближайшие первыми |
Город задаётся словом («Москва», «Питер») и резолвится сам; без него берётся
APTEKI_CITY, иначе Москва.
apteki_where принимает опциональные координаты пользователя (lat, lon):
если они заданы — область поиска и сортировка идут от них (чтобы дойти
побыстрее), иначе от центра города. radius_km (по умолчанию 8) ограничивает
охват; в каждой аптеке есть distance_km от точки отсчёта.
Что показывается по умолчанию
apteki_search и apteki_where по умолчанию сужают выдачу:
- только в наличии (
in_stock_only=true) — отсутствующее скрыто; - только одиночные сети; агрегаторы (Мегаптека, АСНА, ГорЗдрав, 36,6)
скрыты, потому что их выдача пересекается с одиночными сетями и между собой
(ГорЗдрав и 36,6 — почти один бэкенд), и их «в N аптеках» нельзя складывать
или считать разными точками. Скрытые перечислены в ответе (
hidden_aggregators).
Переопределить: include_aggregators=true — добавить агрегаторы;
in_stock_only=false — показать и то, чего сейчас нет.
stock_kind
У каждой площадки в ответе есть stock_kind:
real— остаток на полке конкретной аптеки;delivery— точка выдачи сети, куда заказ привезут (наличия «прямо сейчас» такая площадка не гарантирует).
Разница важна: сервер её проговаривает, чтобы «можно заказать» не выдавалось за «лежит на полке».
Площадки
| Площадка | Города | Аптеки (по товару) | Поиск товара | stock |
|---|---|---|---|---|
| Мегаптека (Platfomni) | вся РФ | ✅ | ✅ цена + охват наличия | real |
| Вита (Platfomni) | вся РФ | ✅ по товару | ✅ цена | real |
| apteka.ru (Протек) | вся РФ, 20+ | ✅ | ❌ анти-бот (410) | delivery |
| Максавит (витрина сайта) | 166 городов | ✅ по товару | ✅ цена + рецептурность | real |
| Ригла (Magento GraphQL) | 68 регионов, 866 городов | ✅ по товару (шт.) | ✅ цена + рецептурность | real |
| АСНА (qHash + анон. OAuth) | вся РФ, 85 регионов | ✅ | ✅ цена + рецептурность + охват | delivery |
ГорЗдрав (SDK ru.sdk, PoW) |
14 регионов | ✅ | ✅ цена + рецептурность + охват | delivery |
Аптеки 36,6 (SDK ru.sdk, PoW) |
8 регионов | ✅ | ✅ цена + рецептурность + охват | delivery |
Как читать столбцы:
-
Аптеки (по товару) — что возвращает
apteki_where:✅— справочник сети: все аптеки города. В какой из них лежит именно искомый препарат — по ответу не видно (есть лишь суммарный охват из поиска, «в N аптеках»);✅ по товару— только те точки, где искомый препарат есть, с ценой в каждой (наличие «есть/нет», без числа упаковок);✅ по товару (шт.)— то же и с количеством на полке («8 шт.»). Пока только Ригла.
-
Поиск товара — работает ли
apteki_search:цена/+ рецептурность/+ охват(«в N аптеках»);❌— ручка закрыта (см. «Что закрыто и почему»). -
stock — это
stock_kind(раздел выше):real— остаток на полке конкретной аптеки,delivery— точка выдачи, куда заказ привезут. -
Мегаптека — агрегатор:
apteki_searchдаёт цену и охват наличия («в 3191 аптеке»). -
Вита — самая точная для
apteki_where: возвращает именно те аптеки, где искомый товар лежит, с адресом и координатами. -
Ригла — то же по остаткам (штуки на точке), плюс сеть «Будь Здоров!», Живика и точки ЗдравСити в одном справочнике. Регион задаётся поддоменом.
-
Максавит — отдаёт аптеки, где товар есть сегодня, и цену в каждой: она своя на город. Товар берётся не из подписанного мобильного API, а с витрины сайта (
maksavit.ru/api/MobileApp) — каталога в мобильном контуре нет вовсе. Москвы в сети нет, ближайший её город — Пушкино; город назван вavailability. -
АСНА — франчайзинговый агрегатор: под её вывеской встречаются и 36,6, и Фармленд, и Планета Здоровья. Поиск даёт цену, рецептурность и охват («в 13382 аптеках»); справочник аптек Москвы — 2270 точек с координатами и метро. Остатка по точке API не отдаёт, поэтому
delivery. -
ГорЗдрав и Аптеки 36,6 — один SDK
ru.sdkс криптографическим антиботом: X25519 + AES-GCM + proof-of-work на каждый запрос (отсюда зависимостьcryptography). Оба дают поиск с ценой и охватом и общий справочник аптек (у ГорЗдрава — 2906 точек с адресом, координатами, метро). Остаток по конкретной точке закрыт за корзиной, поэтомуdelivery.
Разбор протоколов и происхождение констант — в reverse/notes/
(локально; в репозиторий не публикуется).
Что закрыто и почему
- Полнотекстовый поиск apteka.ru закрыт и в мобильном контуре: приложение
ищет тем же
GET /Search/ByPhrase, но с андроид-сессией он отвечает410(тело{ip, requestId, identity, did}), как и/Search/CategoryUrl,/Search/Vendor,/Item/GroupInfo; несуществующий путь даёт404— роуты закрыты намеренно. Запрет не снимают ни свежая сессия, ни форматdevice-id, ни версияUser-Agent, ни город. Открытые ручки фразу не ищут (/Search/AllItemsотдаёт весь каталог), поэтомуsearchтам пуст — остаются города и сеть точек выдачи. - Всё семейство Platfomni вскрыто: Мегаптека/Вита (HMAC-подпись),
Максавит (каталога в подписанном контуре нет — взят с витрины сайта),
ГорЗдрав/36,6 (ушли с HMAC на SDK
ru.sdkс PoW-антиботом), АСНА (своя схема —qHash+ анонимный OAuth Bearer, без SMS). - Приложение Риглы написано на Flutter (логика в
libapp.so), поэтому его разбирать не пришлось: сайт — Magento GraphQL без авторизации и подписи.
Запуск
nix run . # или nix build && ./result/bin/apteki-mcp
Сервер stdio; подключается к ядру Евы как MCP (services.apteki-mcp в NixOS).
Разработка
nix develop # python3 + httpx + cryptography + jadx/apktool/unzip