@retensy/mcp 0.48.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -343,6 +343,14 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
343
343
  | `ai_packs()` | каталог пакетов ответов ИИ сверх тарифа + текущий баланс (`aiPackAnswersLeft`) |
344
344
  | `ai_pack_purchase(sku, method?, confirm:true)` | купить пакет — возвращает ссылку на оплату, деньги не списываются этим вызовом |
345
345
 
346
+ ### Магазин (каталог)
347
+
348
+ | Tool | Назначение |
349
+ |---|---|
350
+ | `catalog_item_upsert(id?, sku?, name?, price?, images?, categoryIds?, categoryNames?, confirm:true)` | создать/обновить товар по `id` или `sku`; `images` — до 10 https-ссылок на фото; `categoryNames` — находит категории по имени или создаёт их |
351
+ | `catalog_category_list()` | категории магазина: `id`, `name`, `parentId` |
352
+ | `catalog_category_upsert(name, id?, parentId?, confirm:true)` | создать (без `id`) или переименовать (с `id`) категорию; без `parentId` при обновлении родитель сохраняется |
353
+
346
354
  ### Рассылки
347
355
 
348
356
  Сообщение рассылки — как в мастере кабинета: `{type, text?, mediaUrl?, mediaUrls?, buttons?}`, до 5 сообщений.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retensy/mcp",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "MCP server to build and publish Telegram and MAX bot funnels/automations, broadcasts, service integrations, block-based websites (incl. free-layout Zero blocks, designs, templates, custom domains, form leads) and Markdown blog articles in the Retensy Bots service. Zero dependencies.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.mjs CHANGED
@@ -1044,6 +1044,9 @@ const TOOLS = [
1044
1044
  { name: "list_bots", description: "Список ботов (id, имя, статус). Включает и свои боты, и боты, в которые тебя пригласили соредактором — у каждого есть accessRole (OWNER/ADMIN/EDITOR/VIEWER); у чужих (не OWNER) есть ownerName — имя владельца проекта. Конфликт токена (тот же Telegram-токен подключён ещё в BotHelp/Salebot/своём скрипте, 10+ минут): pausedReason=TOKEN_CONFLICT (+ pausedAt, pausedConflictHost) — другой сервис держит вебхук, бот active=false (ничего не удалено); pausedChannels[] — у работающего мультиканального бота выключены только эти каналы; conflictWarning=TOKEN_CONFLICT_POLLING (+ conflictWarningUsername) — токен опрашивает кто-то ещё, бот НЕ остановлен, но теряет часть сообщений (снимается само). Чинится вне Retensy (отключить бота там или /revoke в @BotFather + bot_change_token), затем bot_resume для паузы. Доступ = твоя текущая роль в проекте, проверяется на каждый вызов; владелец мог отозвать её без предупреждения.", inputSchema: { type: "object", properties: {} } },
1045
1045
  { name: "list_graphs", description: "Список сценариев САМОГО бота (без узлов). Вебхук-сценарии, которые лишь отвечают через этого бота, сюда не входят — их публикуют в вебе, в «Сценариях» автора.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
1046
1046
  { name: "list_channels", description: "Список каналов/групп, подключённых к боту (chatId, title, type, статус бота, дата). chatId — числовой id для условия SUBSCRIBED («Подписан на канал»).", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
1047
+ { name: "catalog_item_upsert", description: "Создать или обновить товар магазина (POST /api/bots/commerce/items/upsert): по id, иначе по артикулу sku. Для нового товара обязательны name и price (в рублях). images — до 10 https-ссылок на фото (первое — главное). categoryIds — id категорий из catalog_category_list; categoryNames — названия категорий: найдутся по имени или создадутся. stock — остаток (-1 — не учитывать), active — показывать в витрине. Меняет витрину: только по явной просьбе, с confirm:true.", inputSchema: { type: "object", properties: { id: { type: "string", description: "id товара из catalog_list (для обновления)" }, sku: { type: "string", description: "Артикул" }, name: { type: "string", description: "Название" }, description: { type: "string", description: "Описание" }, price: { type: "number", description: "Цена в рублях" }, stock: { type: "integer", description: "Остаток, -1 — не учитывать" }, active: { type: "boolean", description: "Показывать в витрине" }, images: { type: "array", items: { type: "string" }, maxItems: 10, description: "https-ссылки на фото, до 10" }, categoryIds: { type: "array", items: { type: "string" }, description: "id категорий (catalog_category_list)" }, categoryNames: { type: "array", items: { type: "string" }, description: "Названия категорий: существующие находятся по имени, отсутствующие создаются" }, confirm: { type: "boolean" } } } },
1048
+ { name: "catalog_category_list", description: "Категории магазина владельца (GET /api/bots/commerce/categories): id, name, parentId. Только чтение.", inputSchema: { type: "object", properties: {} } },
1049
+ { name: "catalog_category_upsert", description: "Создать или переименовать категорию магазина: с id — обновить (PUT /api/bots/commerce/categories/{id}), без id — создать (POST /api/bots/commerce/categories). parentId — id родительской категории; без него категория верхнего уровня. При обновлении без parentId текущий родитель сохраняется. Меняет витрину: только по явной просьбе, с confirm:true.", inputSchema: { type: "object", properties: { id: { type: "string", description: "id категории из catalog_category_list (для обновления)" }, name: { type: "string", description: "Название категории" }, parentId: { type: "string", description: "id родительской категории" }, confirm: { type: "boolean" } }, required: ["name"] } },
1047
1050
  { name: "integration_catalog", description: "Каталог сервисов Integration Core (GET /api/integrations/catalog): [{provider, name, category, authType, configSchema: [{key, label, hint, secret}] — поля подключения, actions: [{kind, label, inputs}] — действия для сценария и coreDelivery, healthCheck — умеет ли integration_test, inbound: {events: [{key, label, vars: [{key, label, example}], paymentSucceeded}]} | null — провайдер умеет только исходящие действия}]. inbound.events — события для узла TRIGGER_WEBHOOK с заполненным config.provider (см. integration_ingress_url): events[].key идёт в config.event, vars[].key — переменные {{body.<key>}} в сценарии. paymentSucceeded:true — событие сверх обычного вебхука ещё и заводит узел «Оплата прошла» (headless или в чате подписчика, если платёжная ссылка была привязана к нему действием *_link). 404 — Integration Core выключен в сервисе. Read-only.", inputSchema: { type: "object", properties: {} } },
1048
1051
  { name: "integration_status", description: "Статус подключения (GET /api/integrations/{id}/status): {status: UNKNOWN | OK | NEEDS_REAUTH (ключ отозван/устарел — обнови через connect_integration) | ERROR, lastCheckedAt, lastError, supported}. Только своё подключение (чужое — 403). Read-only, внешний сервис не вызывает.", inputSchema: { type: "object", properties: { connectionId: { type: "string", description: "id из list_integrations" } }, required: ["connectionId"] } },
1049
1052
  { name: "integration_test", description: "Проверить подключение (POST /api/integrations/{id}/test): выполняет ЖИВУЮ проверку ключа во внешнем сервисе от имени владельца (без побочных эффектов — ничего не создаёт) и обновляет статус. Ответ как у integration_status; supported:false — сервис проверку не умеет. Только своё подключение (чужое — 403); 404 — Integration Core выключен.", inputSchema: { type: "object", properties: { connectionId: { type: "string", description: "id из list_integrations" } }, required: ["connectionId"] } },
@@ -1307,6 +1310,47 @@ async function handleCall(params) {
1307
1310
  case "list_graphs": return okResult(await api(`/api/bots/${a.botId}/graphs`));
1308
1311
  case "list_channels": return okResult(await api(`/api/bots/${a.botId}/linked-chats`));
1309
1312
  case "list_integrations": return okResult(withoutSecrets(await api("/api/bots/integrations")));
1313
+ case "catalog_item_upsert": {
1314
+ if (a.confirm !== true) throw new Error("Сохранение товара меняет витрину. Спроси пользователя и повтори с confirm:true.");
1315
+ if (a.images !== undefined) {
1316
+ if (!Array.isArray(a.images) || a.images.length > 10) throw new Error("images: массив до 10 https-ссылок");
1317
+ for (const u of a.images) {
1318
+ let ok = false;
1319
+ try { ok = new URL(String(u)).protocol === "https:"; } catch { ok = false; }
1320
+ if (!ok) throw new Error(`images: нужна https-ссылка, получено «${u}»`);
1321
+ }
1322
+ }
1323
+ let categoryIds = a.categoryIds;
1324
+ if (Array.isArray(a.categoryNames) && a.categoryNames.length) {
1325
+ const cats = await api("/api/bots/commerce/categories");
1326
+ categoryIds = Array.isArray(categoryIds) ? [...categoryIds] : [];
1327
+ for (const raw of a.categoryNames) {
1328
+ const catName = String(raw ?? "").trim();
1329
+ if (!catName) continue;
1330
+ let cat = cats.find((c) => String(c.name).toLowerCase() === catName.toLowerCase());
1331
+ if (!cat) { cat = await api("/api/bots/commerce/categories", { method: "POST", body: { name: catName } }); cats.push(cat); }
1332
+ if (!categoryIds.includes(cat.id)) categoryIds.push(cat.id);
1333
+ }
1334
+ }
1335
+ const body = { id: a.id || undefined, sku: a.sku, name: a.name, description: a.description, price: a.price,
1336
+ stock: a.stock, active: a.active, images: a.images, categoryIds };
1337
+ return okResult(await api("/api/bots/commerce/items/upsert", { method: "POST", body }));
1338
+ }
1339
+ case "catalog_category_list": return okResult(await api("/api/bots/commerce/categories"));
1340
+ case "catalog_category_upsert": {
1341
+ if (a.confirm !== true) throw new Error("Изменение категорий меняет витрину. Спроси пользователя и повтори с confirm:true.");
1342
+ const catName = String(a.name ?? "").trim();
1343
+ if (!catName) throw new Error("Укажите название категории.");
1344
+ if (a.id) {
1345
+ let parentId = a.parentId;
1346
+ if (parentId === undefined) {
1347
+ const cats = await api("/api/bots/commerce/categories");
1348
+ parentId = cats.find((c) => c.id === a.id)?.parentId ?? null;
1349
+ }
1350
+ return okResult(await api(`/api/bots/commerce/categories/${encodeURIComponent(String(a.id))}`, { method: "PUT", body: { name: catName, parentId } }));
1351
+ }
1352
+ return okResult(await api("/api/bots/commerce/categories", { method: "POST", body: { name: catName, parentId: a.parentId ?? null } }));
1353
+ }
1310
1354
  case "integration_catalog": return okResult(withoutSecrets(await api("/api/integrations/catalog")));
1311
1355
  case "integration_status":
1312
1356
  case "integration_test": {