@retensy/mcp 0.27.0 → 0.29.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
|
@@ -184,7 +184,7 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
184
184
|
|---|---|
|
|
185
185
|
| `setup` | статус авторизации + пошаговая инструкция подключения |
|
|
186
186
|
| `set_token` | сохранить присланный токен `zmcp_…` (без env/рестарта) |
|
|
187
|
-
| `list_bots` | список ботов |
|
|
187
|
+
| `list_bots` | список ботов (`pausedReason: TOKEN_CONFLICT` / `pausedChannels` — бот или канал на паузе из-за вебхука другого сервиса на том же токене; `conflictWarning: TOKEN_CONFLICT_POLLING` — токен опрашивает кто-то ещё, бот работает, но теряет сообщения) |
|
|
188
188
|
| `create_bot(platform, token?, name?)` | подключить бота Telegram/MAX по токену; `WEB` — чат-виджет для сайта без токена → botId, key, snippet (Instagram → ссылка на кабинет `/bots/instagram`, вход через Instagram) |
|
|
189
189
|
| `web_widget_snippet(botId)` | код вставки чат-виджета на сайт |
|
|
190
190
|
| `bot_stop(botId)` / `bot_resume(botId)` | остановить / запустить бота |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retensy/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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": {
|
|
@@ -57,6 +57,7 @@ description: Собрать воронку (сценарий) бота для с
|
|
|
57
57
|
|
|
58
58
|
5. **Залей и опубликуй через MCP `bot-graph`** (если он подключён и пользователь просит публикацию):
|
|
59
59
|
- `list_bots` → выбрать `botId` (или `create_graph` в существующем боте).
|
|
60
|
+
Конфликт токена (тот же Telegram-токен подключён ещё в BotHelp/Salebot/своём скрипте): `active:false` + `pausedReason:"TOKEN_CONFLICT"` — вебхук другого сервиса, бот на паузе (отвечать не будет); `pausedChannels` — выключены отдельные каналы; `conflictWarning:"TOKEN_CONFLICT_POLLING"` — бот работает, но часть сообщений забирает другая программа. Во всех случаях попроси пользователя отключить бота в том сервисе (или `/revoke` в @BotFather → `bot_change_token`), при паузе — потом `bot_resume`. Справка: `/docs#token-conflict`.
|
|
60
61
|
- `create_graph(botId, name)` → получить `graphId`. Либо стартуй с готовой основы: `list_templates` → `create_graph_from_template(botId, templateId, name)`.
|
|
61
62
|
- `update_graph(graphId, nodes, edges, canvasMeta)` → залить узлы/рёбра.
|
|
62
63
|
- `dry_run(graphId, kind:"command", value:"start")` → прогнать стартовую ветку, проверить `runStatus`. `dry_run` ничего не делает снаружи: внешние запросы, CRM, таблицы, письма, уведомления, счета и ответы ИИ пропускаются (у шага `skipped: "dry-run"`), переменные из их ответов остаются пустыми — это проверяется только живым прогоном.
|
|
@@ -155,6 +155,26 @@ IG-боты не поддерживают команды (`/start`). Вход
|
|
|
155
155
|
- `STOP_AND_ERROR` — `{ "message":"CRM не ответила: {{var.http_status}}" }`. Обрывает прогон и
|
|
156
156
|
помечает его в журнале как ошибочный (`FAILED`), шаг — `ok:false` с этим текстом. Выходов нет,
|
|
157
157
|
ничего подписчику не отправляет. Пустой `message` → «Сценарий остановлен с ошибкой».
|
|
158
|
+
- `DELETE_MESSAGE` — `{ "mode":"PREVIOUS"|"NODE"|"USER_LAST"|"NEXT", "nodeId":"<id узла>", "delay":30, "unit":"SECONDS"|"MINUTES"|"HOURS" }`.
|
|
159
|
+
Удаляет сообщение у подписчика, выход один — `next`. `mode`: `PREVIOUS` — последнее сообщение бота перед этим
|
|
160
|
+
узлом; `NODE` — последнее сообщение узла `nodeId` (узел этого же сценария, который шлёт сообщение:
|
|
161
|
+
`SEND_*`, `ASK_QUESTION`, `AI_REPLY`, `ACTIONS`, оплата); `USER_LAST` — последнее сообщение подписчика
|
|
162
|
+
(только Telegram, в личке); `NEXT` — сообщение, которое отправит следующий отправляющий узел (нужен `delay` > 0).
|
|
163
|
+
`delay`+`unit` — пауза до удаления без остановки сценария (по умолчанию удаляет сразу), меньше 24 часов, допускается
|
|
164
|
+
дробное значение (округляется вверх до секунды).
|
|
165
|
+
**Ограничения.** `NEXT` срабатывает только если следующий узел отправляет сообщение сразу: если между ними `DELAY` или
|
|
166
|
+
`SCHEDULE`, сценарий паркуется, флаг сгорает и ничего не удаляется (валидатор предупреждает `DELETE_NEXT_PARKED`,
|
|
167
|
+
а если отправляющего узла после вообще нет, `DELETE_NEXT_NO_SENDER`). `USER_LAST` работает только в личке Telegram
|
|
168
|
+
(в группах, VK и MAX шаг пропускается с ошибкой в журнале). `NODE`/`PREVIOUS` помнят последнее сообщение узла: если блок
|
|
169
|
+
`ACTIONS` отправил несколько сообщений, удалится только последнее. Отложенные удаления переживают рестарт, остановку
|
|
170
|
+
бота и снятие сценария с публикации.
|
|
171
|
+
Пример «выгнали из канала и удалили сообщение со ссылкой»: `ACTIONS(invite_link_create)` → `ACTIONS(group_kick)` →
|
|
172
|
+
`DELETE_MESSAGE{mode:"NODE", nodeId:<id блока со ссылкой>}`. Сценарий НЕ падает, если удалить нельзя (Telegram даёт
|
|
173
|
+
48 часов на сообщения бота в личке, VK 24 часа, сообщение уже удалено или платформа не умеет): идёт дальше по
|
|
174
|
+
`next`, а шаг помечается ошибкой в журнале и аналитике. Валидатор режет: неизвестный `mode`
|
|
175
|
+
(`DELETE_BAD_MODE`), `NODE` без существующего узла (`DELETE_NODE_MISSING`) или с узлом, который ничего не шлёт
|
|
176
|
+
(`DELETE_NODE_NOT_SENDER`), пауза не число (`DELETE_BAD_DELAY`) или от 24 часов (`DELETE_DELAY_TOO_LONG`),
|
|
177
|
+
`NEXT` без паузы (`DELETE_NEXT_NO_DELAY`). Instagram узел не поддерживает.
|
|
158
178
|
- `ASK_QUESTION` — вопрос со сбором ответа. `{ "promptText":"...","saveTo":"name","inputKind":"TEXT"|"PHOTO"|"DOCUMENT"|"CONTACT"|"LOCATION","validator":"ANY"|"PHONE"|"EMAIL"|"REGEX","regex":"...","retryText":"...","maxAttempts":3 }`. `inputKind` (по умолчанию `TEXT`) — что ждём в ответ (`CONTACT` → телефон: в Telegram показывается кнопка «Поделиться номером», в MAX/Instagram кнопки нет — номер вводится вручную и принимается как телефон на всех платформах; `LOCATION` → `lat,lon`, `PHOTO`/`DOCUMENT` → file_id). Выходы `valid` / `invalid`.
|
|
159
179
|
- `END` — `{}` (конец ветки). **Не добавляй `END`**: ветка и так завершается на узле без исходящих рёбер; явный «конец сценария» бесполезен и убран из палитры редактора. Тип оставлен лишь для совместимости со старыми графами.
|
|
160
180
|
|
|
@@ -483,6 +503,7 @@ node validate.mjs graph.json --platform=INSTAGRAM
|
|
|
483
503
|
| `ACTIONS` | `next`, плюс `error` — если внутри есть действие, которое может упасть (внешний запрос, CRM, Таблицы); `taken` — слот `booking_book` занят |
|
|
484
504
|
| `SWITCH` | `case_<id>`, `default` |
|
|
485
505
|
| `STOP_AND_ERROR` | выходов нет (терминатор) |
|
|
506
|
+
| `DELETE_MESSAGE` | `next` (всегда, даже если удалить не вышло) |
|
|
486
507
|
| `SCHEDULE` | `scheduled`, `past` |
|
|
487
508
|
| `DELAY` | `next`; у `UNTIL` ещё `late` (момент уже прошёл) и `error` (шаблон не разобрался как дата) |
|
|
488
509
|
| `AI_REPLY` (`mode` не задан/`simple`) | `next`, `error` |
|
|
@@ -279,6 +279,50 @@ for (const n of nodes) {
|
|
|
279
279
|
}
|
|
280
280
|
break;
|
|
281
281
|
}
|
|
282
|
+
case "DELETE_MESSAGE": {
|
|
283
|
+
// Зеркало GraphValidator.validateDeleteMessage (Java).
|
|
284
|
+
if (!["NODE", "PREVIOUS", "USER_LAST", "NEXT"].includes(c.mode)) {
|
|
285
|
+
errors.push(`DELETE_BAD_MODE: ${who} — mode ∈ {NODE,PREVIOUS,USER_LAST,NEXT}.`);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
if (c.mode === "NODE") {
|
|
289
|
+
const target = nodes.find((x) => x.id === c.nodeId);
|
|
290
|
+
const SENDERS = ["SEND_MESSAGE", "SEND_PHOTO", "SEND_VIDEO", "SEND_DOCUMENT", "SEND_VOICE", "SEND_VIDEO_NOTE",
|
|
291
|
+
"SEND_MEDIA_GROUP", "ASK_QUESTION", "AI_REPLY", "PAYMENT_LINK", "YOOKASSA_PAYMENT", "ACTIONS"];
|
|
292
|
+
if (!target) errors.push(`DELETE_NODE_MISSING: ${who} — nodeId должен указывать на узел этого сценария.`);
|
|
293
|
+
else if (!SENDERS.includes(target.type)) errors.push(`DELETE_NODE_NOT_SENDER: ${who} — узел nodeId ничего не отправляет.`);
|
|
294
|
+
}
|
|
295
|
+
// Зеркало FlowExecutor.deleteDelaySec: дробная пауза округляется вверх, потолок 24 часа.
|
|
296
|
+
const delay = c.delay === undefined || c.delay === null ? 0 : Number(c.delay);
|
|
297
|
+
const mult = c.unit === "HOURS" ? 3600 : c.unit === "MINUTES" ? 60 : 1;
|
|
298
|
+
const delaySec = Number.isFinite(delay) && delay > 0 ? Math.min(Math.ceil(delay * mult), 86400) : 0;
|
|
299
|
+
if (!Number.isFinite(delay) || delay < 0) errors.push(`DELETE_BAD_DELAY: ${who} — delay должен быть числом не меньше 0.`);
|
|
300
|
+
else if (delaySec >= 86400) errors.push(`DELETE_DELAY_TOO_LONG: ${who} — пауза должна быть меньше 24 часов.`);
|
|
301
|
+
if (c.mode === "NEXT" && delaySec <= 0) errors.push(`DELETE_NEXT_NO_DELAY: ${who} — для mode NEXT нужен delay больше нуля.`);
|
|
302
|
+
if (c.mode === "NEXT") {
|
|
303
|
+
// Флаг «удалить следующее» живёт в одном прогоне: пауза (DELAY/SCHEDULE) до отправки его сжигает.
|
|
304
|
+
const SENDERS_NEXT = ["SEND_MESSAGE", "SEND_PHOTO", "SEND_VIDEO", "SEND_DOCUMENT", "SEND_VOICE", "SEND_VIDEO_NOTE",
|
|
305
|
+
"SEND_MEDIA_GROUP", "ASK_QUESTION", "AI_REPLY", "PAYMENT_LINK", "YOOKASSA_PAYMENT", "ACTIONS"];
|
|
306
|
+
const out = {};
|
|
307
|
+
for (const e of edges) (out[e.sourceNodeId] = out[e.sourceNodeId] || []).push(e.targetNodeId);
|
|
308
|
+
const seen = new Set();
|
|
309
|
+
const queue = [[n.id, false]];
|
|
310
|
+
let senderFound = false, parkedSender = false;
|
|
311
|
+
while (queue.length) {
|
|
312
|
+
const [id, parked] = queue.shift();
|
|
313
|
+
for (const t of out[id] || []) {
|
|
314
|
+
const tn = nodes.find((x) => x.id === t);
|
|
315
|
+
if (!tn || seen.has(`${t}:${parked}`)) continue;
|
|
316
|
+
seen.add(`${t}:${parked}`);
|
|
317
|
+
if (SENDERS_NEXT.includes(tn.type)) { senderFound = true; if (parked) parkedSender = true; }
|
|
318
|
+
else queue.push([t, parked || tn.type === "DELAY" || tn.type === "SCHEDULE"]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (parkedSender) warns.push(`DELETE_NEXT_PARKED: ${who} — между узлом и отправкой есть пауза (DELAY/SCHEDULE): сценарий паркуется, удаление следующего сообщения не сработает.`);
|
|
322
|
+
else if (!senderFound) warns.push(`DELETE_NEXT_NO_SENDER: ${who} — после узла нет блока, который отправляет сообщение.`);
|
|
323
|
+
}
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
282
326
|
case "STOP_AND_ERROR":
|
|
283
327
|
// message необязателен (рантайм подставит свой текст), но безымянная ошибка в журнале бесполезна.
|
|
284
328
|
if (blank(c.message)) warns.push(`${who}: STOP_AND_ERROR без message — в журнале будет обезличенное «Сценарий остановлен с ошибкой».`);
|
package/src/index.mjs
CHANGED
|
@@ -1010,7 +1010,7 @@ const STAGGER_SCHEMA = { type: "object", description: "«Распределит
|
|
|
1010
1010
|
const TOOLS = [
|
|
1011
1011
|
{ name: "setup", description: "Показать статус авторизации и пошаговую инструкцию подключения. Вызывай первым, если пользователь не знает, что делать, или при ошибке доступа. Токен (zmcp_...) определяет АККАУНТ, а не один проект: доступ к каждому боту/сайту — это твоя текущая роль в нём (владелец или приглашённый соредактор), и она проверяется заново при каждом вызове.", inputSchema: { type: "object", properties: {} } },
|
|
1012
1012
|
{ name: "set_token", description: "Сохранить персональный токен (zmcp_...), который пользователь создал на /bots/mcp-tokens. Применяется сразу, без рестарта. Токен определяет аккаунт целиком, не один проект — список доступных проектов (свои + те, куда пригласили) виден в list_bots/site_list. Если владелец проекта уберёт тебя из соредакторов, доступ к этому проекту исчезнет сразу же, без повторной настройки токена.", inputSchema: { type: "object", properties: { token: { type: "string", description: "Секрет токена, начинается с zmcp_" } }, required: ["token"] } },
|
|
1013
|
-
{ name: "list_bots", description: "Список ботов (id, имя, статус). Включает и свои боты, и боты, в которые тебя пригласили соредактором — у каждого есть accessRole (OWNER/ADMIN/EDITOR/VIEWER); у чужих (не OWNER) есть ownerName — имя владельца проекта. Доступ = твоя текущая роль в проекте, проверяется на каждый вызов; владелец мог отозвать её без предупреждения.", inputSchema: { type: "object", properties: {} } },
|
|
1013
|
+
{ 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: {} } },
|
|
1014
1014
|
{ name: "list_graphs", description: "Список сценариев САМОГО бота (без узлов). Вебхук-сценарии, которые лишь отвечают через этого бота, сюда не входят — их публикуют в вебе, в «Сценариях» автора.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1015
1015
|
{ name: "list_channels", description: "Список каналов/групп, подключённых к боту (chatId, title, type, статус бота, дата). chatId — числовой id для условия SUBSCRIBED («Подписан на канал»).", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1016
1016
|
{ 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: {} } },
|
|
@@ -1089,7 +1089,7 @@ const TOOLS = [
|
|
|
1089
1089
|
{ name: "vk_landing_update", description: "Изменить VK-лендинг (PUT /api/growth/vk-landings/{id}) — те же поля, что vk_landing_create, полностью заменяют текущие (не частичный патч).", inputSchema: { type: "object", properties: { id: { type: "string" }, botId: { type: "string" }, title: { type: "string" }, subtitle: { type: "string" }, imageUrl: { type: "string" }, buttonText: { type: "string" }, targetGraphId: { type: "string" }, tags: { type: "array", items: { type: "string" } }, autoSubscribe: { type: "boolean" }, bothelpLandingId: { type: "string" } }, required: ["id", "botId", "title"] } },
|
|
1090
1090
|
{ name: "vk_landing_delete", description: "Удалить VK-лендинг (DELETE /api/growth/vk-landings/{id}) — ссылки перестают работать.", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
|
|
1091
1091
|
{ name: "bot_stop", description: "Остановить бота (POST /api/bots/{botId}/stop): снимает вебхук, бот перестаёт отвечать, сценарии и подписчики сохраняются. Запуск обратно — bot_resume.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1092
|
-
{ name: "bot_resume", description: "Запустить остановленного бота или бота, приостановленного лимитом тарифа (POST /api/bots/{botId}/resume). Если лимит ботов тарифа исчерпан — HTTP 402 со ссылкой на смену тарифа.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1092
|
+
{ name: "bot_resume", description: "Запустить остановленного бота или бота, приостановленного лимитом тарифа или конфликтом токена (POST /api/bots/{botId}/resume). Снимает pausedReason; у работающего бота с pausedChannels включает обратно только эти каналы. Если вебхук другого сервиса остался, через 10 минут пауза повторится — сначала попроси пользователя отключить бота там. Если лимит ботов тарифа исчерпан — HTTP 402 со ссылкой на смену тарифа.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
1093
1093
|
// ---- Подключения ----
|
|
1094
1094
|
{ name: "connect_integration", description: "Подключить сервис (POST /api/bots/integrations) — дальше его id (= connectionId) ставится в действия сценария и в site_lead_settings. provider и creds: AMOCRM {subdomain, longToken} · BITRIX24 {webhookUrl} · GETCOURSE {account, apiKey} · YAMETRIKA {counterId, oauthToken} · YOOKASSA {shopId, secretKey}. Остальные ~25 сервисов (smsru, retailcrm, cloudpayments, rest_api, yandex_market и т.д.) берутся из каталога Integration Core (integration_catalog) — provider передавай его provider-ключом в НИЖНЕМ регистре, нужные creds — из configSchema каждого провайдера (hint подскажет, где взять; поле с «необязательно»/optional в hint можно не слать). Без нужных creds вернёт, какие поля и где их взять. connectionId — обновить креды/название существующего подключения (PUT). Сервисы со входом через браузер не падают, а возвращают ссылку для пользователя: GOOGLE_SHEETS → ссылка согласия Google (OAuth; после неё таблицы выбираются в узле «Google Таблицы»), INSTAGRAM → ссылка на кабинет /bots/instagram (вход через Instagram, нужен Instagram-аккаунт Business/Creator). TELEGRAM/MAX — это боты: используй create_bot. Провайдер каталога со статусом COMING_SOON/IN_DEVELOPMENT подключить нельзя (вернёт ошибку). Если каталог недоступен (офлайн/выключен) — работают только 5 легаси-провайдеров выше. Креды хранятся в сервисе зашифрованными и НИКОГДА не возвращаются — ни здесь, ни в list_integrations (только маска hint); в отчёты не попадают. Проверить ключ после подключения — integration_test.", inputSchema: { type: "object", properties: { provider: { type: "string", description: "AMOCRM | BITRIX24 | GETCOURSE | YAMETRIKA | YOOKASSA | GOOGLE_SHEETS | INSTAGRAM, либо провайдер из integration_catalog (его ключ в нижнем регистре, напр. smsru, retailcrm, cloudpayments)" }, title: { type: "string", description: "Название подключения в кабинете (например «amoCRM продажи»)" }, creds: { type: "object", description: "Поля провайдера: для легаси — см. описание, для остальных — ключи из configSchema провайдера в integration_catalog" }, connectionId: { type: "string", description: "id существующего подключения (list_integrations) — обновить его" } }, required: ["provider"] } },
|
|
1095
1095
|
{ name: "disconnect_integration", description: "Удалить подключение сервиса по id из list_integrations (DELETE /api/bots/integrations/{id}). Действия сценария с этим connectionId перестанут работать.", inputSchema: { type: "object", properties: { connectionId: { type: "string" } }, required: ["connectionId"] } },
|