zaytsv-bot-graph-mcp 0.1.0 → 0.2.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/.claude-plugin/plugin.json +1 -1
- package/README.md +8 -3
- package/package.json +1 -1
- package/skills/build-bot-funnel/SKILL.md +1 -1
- package/src/index.mjs +77 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zaytsv-bot-graph",
|
|
3
3
|
"displayName": "Zaytsv Bot Graph",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "MCP-сервер + скилл для сборки и публикации воронок Telegram-ботов в сервисе zaytsv /bots: из текстового описания → валидный граф → заливка и публикация по API.",
|
|
6
6
|
"author": { "name": "zaytsv", "url": "https://zaytsv.ru" },
|
|
7
7
|
"homepage": "https://zaytsv.ru/bots",
|
package/README.md
CHANGED
|
@@ -53,12 +53,15 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
53
53
|
|
|
54
54
|
1. Залогинься на https://zaytsv.ru → открой **`/bots/mcp-tokens`**.
|
|
55
55
|
2. Создай токен → скопируй секрет `zmcp_...` (показывается один раз).
|
|
56
|
-
3. Передай
|
|
57
|
-
-
|
|
58
|
-
-
|
|
56
|
+
3. Передай токен любым способом:
|
|
57
|
+
- **просто пришли его агенту в чат** — он вызовет инструмент `set_token` и сохранит токен в `~/.zaytsv-bot-graph/token` (применяется сразу, без рестарта), **или**
|
|
58
|
+
- `env` в `.mcp.json` (Вариант B), **или**
|
|
59
|
+
- переменной окружения: PowerShell `setx ZAYTSV_MCP_TOKEN "zmcp_..."`, bash `export ZAYTSV_MCP_TOKEN="zmcp_..."`.
|
|
59
60
|
|
|
60
61
|
Отозвать токен можно там же — доступ блокируется мгновенно.
|
|
61
62
|
|
|
63
|
+
> **Не знаешь, что делать?** Скажи агенту «настрой подключение» — он вызовет `setup`, объяснит шаги и попросит токен. Любой инструмент при отсутствии токена тоже вернёт пошаговую инструкцию.
|
|
64
|
+
|
|
62
65
|
> Дев-окружение: `ZAYTSV_BASE_URL=http://localhost:8066`.
|
|
63
66
|
> Fallback без токена: `ZAYTSV_SESSION_COOKIE` = значение куки `SESSION` из браузера.
|
|
64
67
|
|
|
@@ -76,6 +79,8 @@ MCP-сервер (+ скилл для Claude Code) для **сборки и пу
|
|
|
76
79
|
|
|
77
80
|
| Tool | Назначение |
|
|
78
81
|
|---|---|
|
|
82
|
+
| `setup` | статус авторизации + пошаговая инструкция подключения |
|
|
83
|
+
| `set_token` | сохранить присланный токен `zmcp_…` (без env/рестарта) |
|
|
79
84
|
| `list_bots` | список ботов |
|
|
80
85
|
| `list_graphs(botId)` | графы (сценарии) бота |
|
|
81
86
|
| `get_graph(graphId)` | получить граф |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zaytsv-bot-graph-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "MCP server to build and publish Telegram bot funnels in the zaytsv /bots service. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "zaytsv-bot-graph-mcp": "src/index.mjs" },
|
|
@@ -40,7 +40,7 @@ description: Собрать воронку (сценарий) Telegram-бота
|
|
|
40
40
|
|
|
41
41
|
## Важные ограничения
|
|
42
42
|
- **Источник = текст** (этот режим). Если просят распознать с приватной Miro-доски — самый надёжный путь: CSV-экспорт из Miro; либо запуск залогиненного Chrome пользователя и съёмка экрана (headless WebGL-холст Miro не отдаёт). Это отдельный сценарий, не основной для этого скилла.
|
|
43
|
-
- **Авторизация MCP** — персональный токен
|
|
43
|
+
- **Авторизация MCP** — персональный токен (создаётся в вебе на `/bots/mcp-tokens`, формат `zmcp_…`, полный доступ). Если инструмент вернул «нет токена» или ошибку доступа — **вызови `setup`**, объясни пользователю шаги, попроси прислать токен и сохрани его через **`set_token`** (применяется сразу, без env/рестарта). Также работают env `ZAYTSV_MCP_TOKEN` и session-cookie.
|
|
44
44
|
- Бэкенд читает плоские поля `config.text`/`config.photoUrl`; редактор берёт текст из первой карточки `type:"text"`. Поэтому **всегда заполняй и `text`, и `cards`**.
|
|
45
45
|
|
|
46
46
|
## Файлы скилла
|
package/src/index.mjs
CHANGED
|
@@ -1,39 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* zaytsv-bot-graph-mcp — MCP-сервер для сборки и публикации
|
|
4
|
-
* через API сервиса zaytsv /bots. Без внешних зависимостей (голый JSON-RPC по stdio)
|
|
5
|
-
* поэтому работает сразу, без npm install — в Claude Code, Cursor, Windsurf и т.п.
|
|
3
|
+
* zaytsv-bot-graph-mcp — MCP-сервер для сборки и публикации воронок Telegram-ботов
|
|
4
|
+
* через API сервиса zaytsv /bots. Без внешних зависимостей (голый JSON-RPC по stdio).
|
|
6
5
|
*
|
|
7
|
-
*
|
|
6
|
+
* Авторизация (в порядке приоритета):
|
|
7
|
+
* 1) env ZAYTSV_MCP_TOKEN — персональный токен "zmcp_..."
|
|
8
|
+
* 2) файл ~/.zaytsv-bot-graph/token (заполняется инструментом set_token)
|
|
9
|
+
* 3) session-cookie (ZAYTSV_SESSION_COOKIE / ZAYTSV_COOKIE) — fallback
|
|
10
|
+
*
|
|
11
|
+
* Если токена нет — инструменты не падают с сухой ошибкой, а возвращают пошаговую
|
|
12
|
+
* инструкцию; есть инструменты `setup` (статус + как подключить) и `set_token`
|
|
13
|
+
* (пользователь присылает токен в чат — агент сохраняет его в конфиг, без рестарта).
|
|
8
14
|
*
|
|
9
15
|
* ENV:
|
|
10
|
-
* ZAYTSV_MCP_TOKEN
|
|
11
|
-
* ZAYTSV_BASE_URL база API. По умолчанию https://zaytsv.ru (дев: http://localhost:8066)
|
|
12
|
-
* ZAYTSV_SESSION_COOKIE (fallback) значение куки SESSION из браузера
|
|
13
|
-
* ZAYTSV_COOKIE (fallback) полная строка Cookie
|
|
16
|
+
* ZAYTSV_MCP_TOKEN, ZAYTSV_BASE_URL, ZAYTSV_SESSION_COOKIE, ZAYTSV_COOKIE
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
import { createInterface } from "node:readline";
|
|
20
|
+
import os from "node:os";
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
17
23
|
|
|
18
|
-
const VERSION = "0.
|
|
24
|
+
const VERSION = "0.2.0";
|
|
19
25
|
const BASE = (process.env.ZAYTSV_BASE_URL || "https://zaytsv.ru").replace(/\/+$/, "");
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
const CONFIG_DIR = path.join(os.homedir(), ".zaytsv-bot-graph");
|
|
27
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, "token");
|
|
28
|
+
const TOKENS_PAGE = `${BASE}/bots/mcp-tokens`;
|
|
29
|
+
|
|
30
|
+
function readFileToken() {
|
|
31
|
+
try { return fs.readFileSync(TOKEN_FILE, "utf8").trim(); } catch { return ""; }
|
|
32
|
+
}
|
|
33
|
+
function getToken() {
|
|
34
|
+
return (process.env.ZAYTSV_MCP_TOKEN || "").trim() || readFileToken();
|
|
35
|
+
}
|
|
36
|
+
function getCookie() {
|
|
37
|
+
return process.env.ZAYTSV_COOKIE ||
|
|
38
|
+
(process.env.ZAYTSV_SESSION_COOKIE ? `SESSION=${process.env.ZAYTSV_SESSION_COOKIE}` : "");
|
|
39
|
+
}
|
|
40
|
+
function saveToken(token) {
|
|
41
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
42
|
+
fs.writeFileSync(TOKEN_FILE, token.trim() + "\n", { mode: 0o600 });
|
|
43
|
+
try { fs.chmodSync(TOKEN_FILE, 0o600); } catch { /* windows */ }
|
|
44
|
+
}
|
|
45
|
+
function isAuthed() { return !!(getToken() || getCookie()); }
|
|
46
|
+
|
|
47
|
+
const NO_AUTH_HELP =
|
|
48
|
+
"Нет доступа к zaytsv /bots — не настроена авторизация.\n\n" +
|
|
49
|
+
`Как подключить (помоги пользователю по шагам):\n` +
|
|
50
|
+
`1) Открой ${TOKENS_PAGE} и войди в аккаунт.\n` +
|
|
51
|
+
`2) Нажми «Создать токен», скопируй секрет (вид zmcp_...). Он показывается один раз.\n` +
|
|
52
|
+
`3) Пришли токен сюда — и я вызову инструмент set_token, чтобы сохранить его. (Или задай env ZAYTSV_MCP_TOKEN.)\n\n` +
|
|
53
|
+
"Подсказка ассистенту: попроси пользователя выполнить шаги 1–2 и прислать токен, затем вызови set_token с этим значением.";
|
|
24
54
|
|
|
25
55
|
function authHeaders() {
|
|
26
56
|
const h = { "Content-Type": "application/json" };
|
|
27
|
-
|
|
28
|
-
|
|
57
|
+
const token = getToken();
|
|
58
|
+
const cookie = getCookie();
|
|
59
|
+
if (token) h.Authorization = `Bearer ${token}`;
|
|
60
|
+
else if (cookie) h.Cookie = cookie;
|
|
29
61
|
return h;
|
|
30
62
|
}
|
|
31
63
|
|
|
32
|
-
async function api(
|
|
33
|
-
if (!
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
const res = await fetch(`${BASE}${path}`, {
|
|
64
|
+
async function api(path_, { method = "GET", body } = {}) {
|
|
65
|
+
if (!isAuthed()) throw new Error(NO_AUTH_HELP);
|
|
66
|
+
const res = await fetch(`${BASE}${path_}`, {
|
|
37
67
|
method,
|
|
38
68
|
headers: authHeaders(),
|
|
39
69
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -42,8 +72,12 @@ async function api(path, { method = "GET", body } = {}) {
|
|
|
42
72
|
let data = null;
|
|
43
73
|
try { data = text ? JSON.parse(text) : null; } catch { data = text; }
|
|
44
74
|
if (!res.ok) {
|
|
75
|
+
if (res.status === 401 || res.status === 403) {
|
|
76
|
+
throw new Error(`Доступ отклонён (HTTP ${res.status}). Токен невалиден, отозван или истёк.\n` +
|
|
77
|
+
`Создай новый на ${TOKENS_PAGE} и пришли мне — я сохраню через set_token.`);
|
|
78
|
+
}
|
|
45
79
|
const msg = typeof data === "string" ? data : JSON.stringify(data);
|
|
46
|
-
throw new Error(`${method} ${
|
|
80
|
+
throw new Error(`${method} ${path_} → HTTP ${res.status}. ${(msg || "").slice(0, 600)}`);
|
|
47
81
|
}
|
|
48
82
|
return data;
|
|
49
83
|
}
|
|
@@ -60,6 +94,8 @@ function extractGraph(g) {
|
|
|
60
94
|
}
|
|
61
95
|
|
|
62
96
|
const TOOLS = [
|
|
97
|
+
{ name: "setup", description: "Показать статус авторизации и пошаговую инструкцию подключения. Вызывай первым, если пользователь не знает, что делать, или при ошибке доступа.", inputSchema: { type: "object", properties: {} } },
|
|
98
|
+
{ name: "set_token", description: "Сохранить персональный токен (zmcp_...), который пользователь создал на /bots/mcp-tokens. Применяется сразу, без рестарта.", inputSchema: { type: "object", properties: { token: { type: "string", description: "Секрет токена, начинается с zmcp_" } }, required: ["token"] } },
|
|
63
99
|
{ name: "list_bots", description: "Список ботов пользователя (id, имя, статус).", inputSchema: { type: "object", properties: {} } },
|
|
64
100
|
{ name: "list_graphs", description: "Список графов (сценариев) бота.", inputSchema: { type: "object", properties: { botId: { type: "string" } }, required: ["botId"] } },
|
|
65
101
|
{ name: "get_graph", description: "Получить граф целиком по graphId.", inputSchema: { type: "object", properties: { graphId: { type: "string" } }, required: ["graphId"] } },
|
|
@@ -73,6 +109,25 @@ const TOOLS = [
|
|
|
73
109
|
async function handleCall(params) {
|
|
74
110
|
const a = (params && params.arguments) || {};
|
|
75
111
|
switch (params && params.name) {
|
|
112
|
+
case "setup": {
|
|
113
|
+
if (isAuthed()) {
|
|
114
|
+
const via = getToken() ? "персональный токен" : "session-cookie";
|
|
115
|
+
return okResult(`✅ Авторизация настроена (${via}). База API: ${BASE}.\n` +
|
|
116
|
+
`Можно собирать и публиковать ботов: list_bots, create_graph, import_funnel и др.`);
|
|
117
|
+
}
|
|
118
|
+
return okResult(NO_AUTH_HELP);
|
|
119
|
+
}
|
|
120
|
+
case "set_token": {
|
|
121
|
+
const t = (a.token || "").trim();
|
|
122
|
+
if (!t) throw new Error("Передай token — секрет вида zmcp_..., который ты создал на " + TOKENS_PAGE);
|
|
123
|
+
saveToken(t);
|
|
124
|
+
const warn = t.startsWith("zmcp_") ? "" : "\n⚠️ Обычно токен начинается с «zmcp_» — проверь, что скопирован весь секрет.";
|
|
125
|
+
// лёгкая проверка валидности
|
|
126
|
+
let check = "";
|
|
127
|
+
try { const bots = await api("/api/tg/bots"); check = `\nПроверка: доступно ботов — ${Array.isArray(bots) ? bots.length : "?"}.`; }
|
|
128
|
+
catch (e) { check = `\n⚠️ Токен сохранён, но проверка не прошла: ${(e.message || "").split("\n")[0]}`; }
|
|
129
|
+
return okResult(`✅ Токен сохранён (${TOKEN_FILE}). Применяется сразу.${warn}${check}`);
|
|
130
|
+
}
|
|
76
131
|
case "list_bots": return okResult(await api("/api/tg/bots"));
|
|
77
132
|
case "list_graphs": return okResult(await api(`/api/tg/bots/${a.botId}/graphs`));
|
|
78
133
|
case "get_graph": return okResult(await api(`/api/tg/graphs/${a.graphId}`));
|
|
@@ -138,7 +193,6 @@ rl.on("line", async (line) => {
|
|
|
138
193
|
} else if (method === "ping") {
|
|
139
194
|
send({ jsonrpc: "2.0", id, result: {} });
|
|
140
195
|
} else if (id !== undefined && id !== null) {
|
|
141
|
-
// неизвестный метод с id — корректный JSON-RPC error; нотификации игнорируем
|
|
142
196
|
send({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
|
|
143
197
|
}
|
|
144
198
|
} catch (e) {
|
|
@@ -146,4 +200,4 @@ rl.on("line", async (line) => {
|
|
|
146
200
|
}
|
|
147
201
|
});
|
|
148
202
|
|
|
149
|
-
process.stderr.write(`[zaytsv-bot-graph] MCP ${VERSION}. BASE=${BASE}. Авторизация: ${
|
|
203
|
+
process.stderr.write(`[zaytsv-bot-graph] MCP ${VERSION}. BASE=${BASE}. Авторизация: ${getToken() ? "токен" : getCookie() ? "cookie" : "не задана (вызови setup)"}.\n`);
|