suare-mcp 1.0.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/.env.example +13 -0
- package/.env.production +5 -0
- package/README.md +99 -0
- package/index.js +890 -0
- package/openapi.json +180 -0
- package/package.json +24 -0
- package/test-client.js +66 -0
package/.env.example
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SUARE MCP Server Environment Configuration (.env.example)
|
|
2
|
+
|
|
3
|
+
# URL бэкенд API SUARE
|
|
4
|
+
SUARE_API_URL="https://suare-server.vercel.app"
|
|
5
|
+
|
|
6
|
+
# Публичный URL клиентского сайта SUARE
|
|
7
|
+
SUARE_WEB_URL="https://suaree.vercel.app"
|
|
8
|
+
|
|
9
|
+
# Порт для запуска HTTP/SSE сервера (для ChatGPT Actions и n8n)
|
|
10
|
+
PORT=3800
|
|
11
|
+
|
|
12
|
+
# Режим транспорта: "stdio" (по умолчанию для IDE) или "http"
|
|
13
|
+
MCP_TRANSPORT="stdio"
|
package/.env.production
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# SUARE MCP Server (Model Context Protocol)
|
|
2
|
+
|
|
3
|
+
> **Назначение:** Позволяет ИИ-моделям (**ChatGPT, Claude, Antigravity IDE, Cursor, n8n**) быстро создавать цифровые пригласительные на тои и свадьбы в SUARE (`suare.app`), управлять деталями торжества, настраивать списки гостей и получать прямые ссылки на превью и оплату прямо из чата.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Доступные инструменты (Tools)
|
|
8
|
+
|
|
9
|
+
| Инструмент | Описание |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `suare_list_templates` | Возвращает каталог шаблонов (40+ дизайнов) с фильтрацией по категориям (Свадьба, Қыз ұзату, Сүндет той, Тұсау кесер, Мерейтой) и точной стоимостью напрямую из базы данных. |
|
|
12
|
+
| `suare_create_invitation` | Создает цифровое пригласительное по данным из диалога. Генерирует ссылку для гостей (`suare.app/i/slug`), ссылку на конструктор, превью-карточку, стоимость из БД и готовый текст для WhatsApp. |
|
|
13
|
+
| `suare_get_invitation` | Возвращает данные пригласительного и статус подтверждений гостей (RSVP). |
|
|
14
|
+
| `suare_update_invitation` | Вносит изменения в дату, зал, имена, дресс-код или меняет шаблон оформления. |
|
|
15
|
+
| `suare_manage_guests` | Добавление гостей текстом или массивом, получение структуры для массового импорта через Excel. |
|
|
16
|
+
| `suare_get_payment_info` | Возвращает точную сумму из базы данных, прямую ссылку на оплату без регистрации (`suare.app/pay/editToken`) и реквизиты Kaspi Pay / QR. |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 2. Подключение к ИИ-клиентам
|
|
21
|
+
|
|
22
|
+
### Вариант А: Antigravity IDE (Уже подключено!)
|
|
23
|
+
В файле `~/.gemini/config/mcp_config.json`:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"suare": {
|
|
28
|
+
"command": "node",
|
|
29
|
+
"args": ["c:\\Users\\Akim.O\\Documents\\Личные\\проект\\suare new\\suare-mcp\\index.js"],
|
|
30
|
+
"env": {
|
|
31
|
+
"SUARE_API_URL": "http://localhost:4000",
|
|
32
|
+
"SUARE_WEB_URL": "https://suare.app"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### Вариант Б: Claude Desktop
|
|
42
|
+
Откройте файл конфигурации Claude Desktop:
|
|
43
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
44
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
45
|
+
|
|
46
|
+
Добавьте секцию `mcpServers`:
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"suare": {
|
|
51
|
+
"command": "node",
|
|
52
|
+
"args": ["c:\\Users\\Akim.O\\Documents\\Личные\\проект\\suare new\\suare-mcp\\index.js"],
|
|
53
|
+
"env": {
|
|
54
|
+
"SUARE_WEB_URL": "https://suare.app"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
Перезапустите Claude Desktop — в меню появится значок молоточка с инструментами `suare_*`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### Вариант В: ChatGPT (Custom GPT Store / Actions)
|
|
65
|
+
Для использования в ChatGPT:
|
|
66
|
+
1. Запустите HTTP-режим:
|
|
67
|
+
```bash
|
|
68
|
+
npm run start:http
|
|
69
|
+
# Сервер запустится на http://localhost:3800
|
|
70
|
+
```
|
|
71
|
+
2. Откройте [chatgpt.com/gpts/editor](https://chatgpt.com/gpts/editor) (GPT Builder).
|
|
72
|
+
3. Во вкладке **Configure** укажите:
|
|
73
|
+
- **Name:** SUARE — Онлайн-шақыру жасау
|
|
74
|
+
- **Description:** ИИ-помощник для создания стильных свадебных онлайн-пригласительных за 2 минуты.
|
|
75
|
+
- **Instructions:**
|
|
76
|
+
```text
|
|
77
|
+
Ты — официальный ИИ-ассистент платформы SUARE (suare.app).
|
|
78
|
+
Помогай пользователям создавать цифровые пригласительные на тои, свадьбы, қыз ұзату, сүндет той и юбилеи в Казахстане.
|
|
79
|
+
Общайся вежливо на казахском или русском языке.
|
|
80
|
+
Спрашивай имена главных героев, дату, ресторан/зал и тип тоя.
|
|
81
|
+
Вызывай действие createInvitation и отправляй пользователю превью-карточку, ссылку для гостей и текст для WhatsApp!
|
|
82
|
+
```
|
|
83
|
+
4. В разделе **Actions** нажмите **Create new action** и вставьте содержимое файла:
|
|
84
|
+
`suare-mcp/openapi.json`.
|
|
85
|
+
5. Готово! Пользователи могут писать боту прямо в ChatGPT на телефоне или компьютере.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Вариант Г: n8n (AI Agent Node)
|
|
90
|
+
В n8n подключите ноду **AI Agent** и добавьте инструмент **Custom MCP Tool**, указав URL:
|
|
91
|
+
`http://localhost:3800/mcp` или прямой Webhook.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 3. Проверка работоспособности
|
|
96
|
+
```bash
|
|
97
|
+
node test-client.js
|
|
98
|
+
```
|
|
99
|
+
Выведет `✅ All MCP JSON-RPC 2.0 tests passed successfully!`.
|
package/index.js
ADDED
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ==============================================================================
|
|
3
|
+
// SUARE MCP SERVER (Model Context Protocol)
|
|
4
|
+
// Provides tools for LLMs (ChatGPT, Claude, Antigravity) to browse templates,
|
|
5
|
+
// create digital event invitations, manage guest lists, and handle RSVPs directly from chat.
|
|
6
|
+
//
|
|
7
|
+
// Transports:
|
|
8
|
+
// 1. Stdio (Standard Input/Output) - for Claude Desktop, Antigravity IDE, Cursor
|
|
9
|
+
// 2. HTTP / SSE - for remote clients, ChatGPT Developer Mode, n8n, webhooks
|
|
10
|
+
// ==============================================================================
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
const http = require("http");
|
|
15
|
+
const readline = require("readline");
|
|
16
|
+
|
|
17
|
+
// Configuration
|
|
18
|
+
const SUARE_API_URL = process.env.SUARE_API_URL || "https://suare-server.vercel.app";
|
|
19
|
+
const SUARE_WEB_URL = process.env.SUARE_WEB_URL || "https://suaree.vercel.app";
|
|
20
|
+
const HTTP_PORT = parseInt(process.env.PORT || "3800", 10);
|
|
21
|
+
const IS_HTTP_MODE = process.argv.includes("--http") || process.env.MCP_TRANSPORT === "http";
|
|
22
|
+
|
|
23
|
+
// Local in-memory invitation registry (persisted or synchronized with backend)
|
|
24
|
+
const memoryStore = new Map();
|
|
25
|
+
|
|
26
|
+
// Helper: Slugify names
|
|
27
|
+
function slugify(text) {
|
|
28
|
+
const ruMap = {
|
|
29
|
+
а: "a", б: "b", в: "v", г: "g", д: "d", е: "e", ё: "yo", ж: "zh",
|
|
30
|
+
з: "z", и: "i", й: "y", к: "k", л: "l", м: "m", н: "n", о: "o",
|
|
31
|
+
п: "p", р: "r", с: "s", т: "t", у: "u", ф: "f", х: "kh", ц: "ts",
|
|
32
|
+
ч: "ch", ш: "sh", щ: "shch", ъ: "", ы: "y", ь: "", э: "e", ю: "yu", я: "ya",
|
|
33
|
+
ә: "a", ғ: "g", қ: "q", ң: "n", ө: "o", ұ: "u", ү: "u", һ: "h", і: "i",
|
|
34
|
+
" ": "-", "&": "and", "+": "and",
|
|
35
|
+
};
|
|
36
|
+
return text
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.split("")
|
|
39
|
+
.map((c) => (ruMap[c] !== undefined ? ruMap[c] : c))
|
|
40
|
+
.join("")
|
|
41
|
+
.replace(/[^a-z0-9-]/g, "")
|
|
42
|
+
.replace(/-+/g, "-")
|
|
43
|
+
.replace(/^-|-$/g, "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ==============================================================================
|
|
47
|
+
// 1. DYNAMIC TEMPLATES CATALOG (Loaded from database / generated json)
|
|
48
|
+
// ==============================================================================
|
|
49
|
+
const generatedFile = path.join(__dirname, "..", "suare-server", "prisma", "templates.generated.json");
|
|
50
|
+
|
|
51
|
+
function getTemplates() {
|
|
52
|
+
if (fs.existsSync(generatedFile)) {
|
|
53
|
+
try {
|
|
54
|
+
const raw = JSON.parse(fs.readFileSync(generatedFile, "utf8"));
|
|
55
|
+
return raw.map((t) => ({
|
|
56
|
+
slug: t.slug,
|
|
57
|
+
nameRu: t.nameRu || t.nameKk,
|
|
58
|
+
nameKk: t.nameKk || t.nameRu,
|
|
59
|
+
descriptionRu: t.descriptionRu || "",
|
|
60
|
+
descriptionKk: t.descriptionKk || "",
|
|
61
|
+
categories: t.categories || ["WEDDING"],
|
|
62
|
+
priceTenge: typeof t.priceTenge === "number" ? t.priceTenge : 0,
|
|
63
|
+
tags: t.tags || ["animated"],
|
|
64
|
+
previewUrl: `${SUARE_WEB_URL}/templates/${t.slug}.png`,
|
|
65
|
+
}));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.warn("Could not parse templates.generated.json, using fallback");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return FALLBACK_TEMPLATES;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getTemplateBySlug(slug) {
|
|
74
|
+
const all = getTemplates();
|
|
75
|
+
return all.find((t) => t.slug === slug) || null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const FALLBACK_TEMPLATES = [
|
|
79
|
+
{
|
|
80
|
+
slug: "altyn-oyu",
|
|
81
|
+
nameRu: "Золотой орнамент (Алтын ою)",
|
|
82
|
+
nameKk: "Алтын ою",
|
|
83
|
+
descriptionRu: "Премиальный темный оникс с благородным золотым казахским орнаментом и золотыми искрами.",
|
|
84
|
+
categories: ["WEDDING", "ANNIVERSARY"],
|
|
85
|
+
priceTenge: 5990,
|
|
86
|
+
tags: ["dark", "national", "gold", "animated"],
|
|
87
|
+
previewUrl: `${SUARE_WEB_URL}/templates/altyn-oyu.png`,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
slug: "aru-qyz",
|
|
91
|
+
nameRu: "Ару қыз",
|
|
92
|
+
nameKk: "Ару қыз",
|
|
93
|
+
descriptionRu: "Нежный пудровый стиль с акварельными цветами и плавной анимацией для проводов невесты.",
|
|
94
|
+
categories: ["KYZ_UZATU", "WEDDING"],
|
|
95
|
+
priceTenge: 3990,
|
|
96
|
+
tags: ["light", "romantic", "floral", "animated"],
|
|
97
|
+
previewUrl: `${SUARE_WEB_URL}/templates/aru-qyz.png`,
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
slug: "saltanat-kesh",
|
|
101
|
+
nameRu: "Торжественный вечер (Салтанат кеші)",
|
|
102
|
+
nameKk: "Салтанат кеші",
|
|
103
|
+
descriptionRu: "Вечерний глубокий стиль с акцентом на фотосессию Love Story и мерцающие золотые огни.",
|
|
104
|
+
categories: ["WEDDING", "ANNIVERSARY"],
|
|
105
|
+
priceTenge: 5990,
|
|
106
|
+
tags: ["dark", "photo", "luxury", "animated"],
|
|
107
|
+
previewUrl: `${SUARE_WEB_URL}/templates/saltanat-kesh.png`,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
slug: "batyr-tulpar",
|
|
111
|
+
nameRu: "Батыр тұлпар",
|
|
112
|
+
nameKk: "Батыр тұлпар",
|
|
113
|
+
descriptionRu: "Торжественный национальный стиль с крылатым скакуном и традиционными узорами для сүндет тоя.",
|
|
114
|
+
categories: ["SUNDET_TOI"],
|
|
115
|
+
priceTenge: 3990,
|
|
116
|
+
tags: ["light", "national", "kids", "animated"],
|
|
117
|
+
previewUrl: `${SUARE_WEB_URL}/templates/batyr-tulpar.png`,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
slug: "algashqy-qadam",
|
|
121
|
+
nameRu: "Алғашқы қадам (Первые шаги)",
|
|
122
|
+
nameKk: "Алғашқы қадам",
|
|
123
|
+
descriptionRu: "Светлый и трогательный дизайн с белой дорожкой и традиционным ала жіп для тұсау кесер.",
|
|
124
|
+
categories: ["TUSAU_KESER"],
|
|
125
|
+
priceTenge: 3990,
|
|
126
|
+
tags: ["light", "kids", "animated"],
|
|
127
|
+
previewUrl: `${SUARE_WEB_URL}/templates/algashqy-qadam.png`,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
slug: "altyn-zhas",
|
|
131
|
+
nameRu: "Золотой юбилей (Алтын жас)",
|
|
132
|
+
nameKk: "Алтын жас",
|
|
133
|
+
descriptionRu: "Солидный изумрудно-золотой дизайн для уважаемых юбиляров (50, 60, 70 лет).",
|
|
134
|
+
categories: ["ANNIVERSARY", "JUBILEE"],
|
|
135
|
+
priceTenge: 5990,
|
|
136
|
+
tags: ["dark", "gold", "prestige", "animated"],
|
|
137
|
+
previewUrl: `${SUARE_WEB_URL}/templates/altyn-zhas.png`,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const CATEGORY_NAMES = {
|
|
142
|
+
WEDDING: { titleRu: "Үйлену той (Свадьба)", titleKk: "Үйлену тойы", emoji: "👰🤵" },
|
|
143
|
+
KYZ_UZATU: { titleRu: "Қыз ұзату (Проводы невесты)", titleKk: "Қыз ұзату", emoji: "🌸" },
|
|
144
|
+
SUNDET_TOI: { titleRu: "Сүндет той", titleKk: "Сүндет тойы", emoji: "🐎" },
|
|
145
|
+
SUNDET_TOY: { titleRu: "Сүндет той", titleKk: "Сүндет тойы", emoji: "🐎" },
|
|
146
|
+
TUSAU_KESER: { titleRu: "Тұсау кесер", titleKk: "Тұсау кесер", emoji: "👣" },
|
|
147
|
+
BESIK_TOI: { titleRu: "Бесік той", titleKk: "Бесік тойы", emoji: "👶" },
|
|
148
|
+
SHILDEKHANA: { titleRu: "Шілдехана", titleKk: "Шілдехана", emoji: "🍼" },
|
|
149
|
+
QYRQYNAN: { titleRu: "Қырқынан шығару", titleKk: "Қырқынан шығару", emoji: "✨" },
|
|
150
|
+
TILASHAR: { titleRu: "Тілашар", titleKk: "Тілашар", emoji: "🎒" },
|
|
151
|
+
ANNIVERSARY: { titleRu: "Мерейтой / Юбилей", titleKk: "Мерейтой", emoji: "🎂" },
|
|
152
|
+
JUBILEE: { titleRu: "Мерейтой / Юбилей", titleKk: "Мерейтой", emoji: "🎂" },
|
|
153
|
+
BIRTHDAY: { titleRu: "Туған күн (День рождения)", titleKk: "Туған күн", emoji: "🎉" },
|
|
154
|
+
QONYS_TOI: { titleRu: "Қоныс той (Новоселье)", titleKk: "Қоныс той", emoji: "🏡" },
|
|
155
|
+
AUYZASHAR: { titleRu: "Ауызашар", titleKk: "Ауызашар", emoji: "🌙" },
|
|
156
|
+
CORPORATE: { titleRu: "Корпоратив", titleKk: "Корпоратив", emoji: "💼" },
|
|
157
|
+
GATHERING: { titleRu: "Бас қосу / Қонақасы", titleKk: "Бас қосу", emoji: "☕" },
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// ==============================================================================
|
|
161
|
+
// 2. TOOLS DEFINITION (MCP SPECIFICATION)
|
|
162
|
+
// ==============================================================================
|
|
163
|
+
const TOOLS = [
|
|
164
|
+
{
|
|
165
|
+
name: "suare_list_templates",
|
|
166
|
+
description:
|
|
167
|
+
"Возвращает структурированный каталог дизайнерских шаблонов SUARE (40+ шаблонов), сгруппированных по категориям тоев (Үйлену той, Қыз ұзату, Сүндет той, Тұсау кесер, Мерейтой, Бесік той, Қоныс той и др.) с актуальной стоимостью из базы данных, стилями и живыми демо-ссылками. Используйте этот инструмент, когда пользователь выбирает дизайн или спрашивает, какие шаблоны есть.",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
category: {
|
|
172
|
+
type: "string",
|
|
173
|
+
description:
|
|
174
|
+
"Категория торжества для фильтрации (ALL, WEDDING, KYZ_UZATU, SUNDET_TOI, TUSAU_KESER, BESIK_TOI, ANNIVERSARY, BIRTHDAY, QONYS_TOI, AUYZASHAR, CORPORATE, TILASHAR, SHILDEKHANA, QYRQYNAN, GATHERING)",
|
|
175
|
+
default: "ALL",
|
|
176
|
+
},
|
|
177
|
+
style: {
|
|
178
|
+
type: "string",
|
|
179
|
+
enum: ["ALL", "national", "gold", "dark", "light", "minimal", "floral", "photo"],
|
|
180
|
+
description: "Желаемый стиль оформления (national - казахский орнамент, gold - золото, dark - ночной премиум, light - светлый, floral - акварель/цветы, photo - с фотосессией)",
|
|
181
|
+
default: "ALL",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "suare_create_invitation",
|
|
188
|
+
description:
|
|
189
|
+
"Создает новое онлайн-пригласительное в SUARE по данным из диалога. Возвращает готовую ссылку для гостей (suare.app/i/slug), ссылку на конструктор, карточку-превью и текст для отправки в WhatsApp.",
|
|
190
|
+
inputSchema: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
type: {
|
|
194
|
+
type: "string",
|
|
195
|
+
enum: ["WEDDING", "QYZ_UZATU", "SUNDET_TOY", "JUBILEE", "BIRTHDAY"],
|
|
196
|
+
description: "Тип мероприятия (напр. WEDDING для свадьбы, QYZ_UZATU для проводов)",
|
|
197
|
+
},
|
|
198
|
+
hostNames: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Имена главных героев (напр. 'Азамат & Аружан', 'Алихан')",
|
|
201
|
+
},
|
|
202
|
+
organizers: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "Той иелері / Организаторы (напр. 'Марат - Гүлнар', 'Ата-анасы')",
|
|
205
|
+
},
|
|
206
|
+
startsAt: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "Дата и время в формате ISO или YYYY-MM-DD HH:mm (напр. '2026-08-15 18:00')",
|
|
209
|
+
},
|
|
210
|
+
venueName: {
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "Название банкетного зала или ресторана (напр. 'Ritz-Carlton Almaty', 'Салтанат Сарайы')",
|
|
213
|
+
},
|
|
214
|
+
venueAddress: {
|
|
215
|
+
type: "string",
|
|
216
|
+
description: "Адрес проведения (город, улица)",
|
|
217
|
+
},
|
|
218
|
+
venueMapUrl: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "Ссылка на 2GIS или Яндекс.Карты (опционально)",
|
|
221
|
+
},
|
|
222
|
+
templateSlug: {
|
|
223
|
+
type: "string",
|
|
224
|
+
description: "Slug выбранного шаблона (altyn-orda, minimal-rose, classic-gold и др.)",
|
|
225
|
+
},
|
|
226
|
+
dressCode: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Пожелания по нарядам гостей (напр. 'Black Tie', 'Пастельные тона')",
|
|
229
|
+
},
|
|
230
|
+
customSlug: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Желаемый красивый URL адрес (напр. 'azamat-aruzhan')",
|
|
233
|
+
},
|
|
234
|
+
program: {
|
|
235
|
+
type: "array",
|
|
236
|
+
description: "Таймлайн программы вечера",
|
|
237
|
+
items: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
time: { type: "string", description: "Время (напр. '18:00')" },
|
|
241
|
+
title: { type: "string", description: "Событие (напр. 'Сбор гостей', 'Беташар')" },
|
|
242
|
+
},
|
|
243
|
+
required: ["time", "title"],
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
required: ["type", "hostNames", "startsAt", "venueName"],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "suare_get_invitation",
|
|
252
|
+
description:
|
|
253
|
+
"Получает информацию об уже созданном пригласительном, статус RSVP (список подтвердивших гостей, количество взрослых и детей).",
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
slug: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "Короткий адрес пригласительного (напр. 'azamat-aruzhan')",
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
required: ["slug"],
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "suare_update_invitation",
|
|
267
|
+
description:
|
|
268
|
+
"Вносит изменения в уже созданное пригласительное по его slug или editToken: изменение зала, даты, имен, программы, дресс-кода или смена шаблона оформления.",
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
slug: { type: "string", description: "Slug пригласительного" },
|
|
273
|
+
hostNames: { type: "string", description: "Обновленные имена виновников торжества" },
|
|
274
|
+
startsAt: { type: "string", description: "Новая дата и время" },
|
|
275
|
+
venueName: { type: "string", description: "Новое название зала/ресторана" },
|
|
276
|
+
venueAddress: { type: "string", description: "Новый адрес" },
|
|
277
|
+
templateSlug: { type: "string", description: "Новый шаблон оформления (altyn-orda, minimal-rose, classic-gold, emerald-luxury)" },
|
|
278
|
+
dressCode: { type: "string", description: "Дресс-код" },
|
|
279
|
+
},
|
|
280
|
+
required: ["slug"],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "suare_manage_guests",
|
|
285
|
+
description:
|
|
286
|
+
"Управление списком гостей: добавление гостей списком/текстом, просмотр таблицы приглашенных или получение ссылки на шаблонный Excel-файл для массовой загрузки.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
slug: { type: "string", description: "Slug пригласительного" },
|
|
291
|
+
action: {
|
|
292
|
+
type: "string",
|
|
293
|
+
enum: ["ADD", "LIST", "DOWNLOAD_TEMPLATE"],
|
|
294
|
+
description: "Действие: ADD (добавить гостей), LIST (посмотреть список), DOWNLOAD_TEMPLATE (ссылка на Excel-шаблон)",
|
|
295
|
+
default: "LIST",
|
|
296
|
+
},
|
|
297
|
+
guests: {
|
|
298
|
+
type: "array",
|
|
299
|
+
description: "Список гостей для добавления",
|
|
300
|
+
items: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
name: { type: "string", description: "ФИО или обращение к гостю (напр. 'Асқар аға')" },
|
|
304
|
+
phone: { type: "string", description: "Номер телефона (напр. '+7 701 123 4567')" },
|
|
305
|
+
seats: { type: "integer", description: "Количество персон (по умолчанию 1)", default: 1 },
|
|
306
|
+
group: { type: "string", description: "Категория гостей (напр. 'Құдалар', 'Достар', 'Әріптестер')" },
|
|
307
|
+
},
|
|
308
|
+
required: ["name"],
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
required: ["slug"],
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
name: "suare_get_payment_info",
|
|
317
|
+
description:
|
|
318
|
+
"Возвращает точную стоимость выбранного шаблона, прямую ссылку на моментальную оплату без предварительной регистрации (suare.app/pay/editToken) и реквизиты Kaspi Pay / QR.",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
slug: { type: "string", description: "Slug пригласительного" },
|
|
323
|
+
},
|
|
324
|
+
required: ["slug"],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
// ==============================================================================
|
|
330
|
+
// 3. TOOL EXECUTION HANDLERS
|
|
331
|
+
// ==============================================================================
|
|
332
|
+
async function handleToolCall(name, args) {
|
|
333
|
+
switch (name) {
|
|
334
|
+
case "suare_list_templates": {
|
|
335
|
+
let rawCat = (args.category || "ALL").toUpperCase().trim();
|
|
336
|
+
const rawStyle = (args.style || "ALL").toLowerCase().trim();
|
|
337
|
+
|
|
338
|
+
// Normalize category aliases
|
|
339
|
+
const categoryAliases = {
|
|
340
|
+
"СВАДЬБА": "WEDDING",
|
|
341
|
+
"ҮЙЛЕНУ": "WEDDING",
|
|
342
|
+
"ҮЙЛЕНУ ТОЙ": "WEDDING",
|
|
343
|
+
"QYZ_UZATU": "KYZ_UZATU",
|
|
344
|
+
"ҚЫЗ ҰЗАТУ": "KYZ_UZATU",
|
|
345
|
+
"ПРОВОДЫ": "KYZ_UZATU",
|
|
346
|
+
"СҮНДЕТ": "SUNDET_TOI",
|
|
347
|
+
"SUNDET_TOY": "SUNDET_TOI",
|
|
348
|
+
"СҮНДЕТ ТОЙ": "SUNDET_TOI",
|
|
349
|
+
"ТҰСАУ": "TUSAU_KESER",
|
|
350
|
+
"ТҰСАУ КЕСЕР": "TUSAU_KESER",
|
|
351
|
+
"БЕСІК": "BESIK_TOI",
|
|
352
|
+
"БЕСІК ТОЙ": "BESIK_TOI",
|
|
353
|
+
"ШІЛДЕХАНА": "SHILDEKHANA",
|
|
354
|
+
"ҚЫРҚЫНАН": "QYRQYNAN",
|
|
355
|
+
"ТІЛАШАР": "TILASHAR",
|
|
356
|
+
"МЕРЕЙТОЙ": "ANNIVERSARY",
|
|
357
|
+
"ЮБИЛЕЙ": "ANNIVERSARY",
|
|
358
|
+
"JUBILEE": "ANNIVERSARY",
|
|
359
|
+
"ТУҒАН КҮН": "BIRTHDAY",
|
|
360
|
+
"ДЕНЬ РОЖДЕНИЯ": "BIRTHDAY",
|
|
361
|
+
"ҚОНЫС": "QONYS_TOI",
|
|
362
|
+
"НОВОСЕЛЬЕ": "QONYS_TOI",
|
|
363
|
+
"АУЫЗАШАР": "AUYZASHAR",
|
|
364
|
+
"ИФТАР": "AUYZASHAR",
|
|
365
|
+
"КОРПОРАТИВ": "CORPORATE",
|
|
366
|
+
"БАС ҚОСУ": "GATHERING",
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
for (const [alias, canonical] of Object.entries(categoryAliases)) {
|
|
370
|
+
if (rawCat.includes(alias)) {
|
|
371
|
+
rawCat = canonical;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let filtered = getTemplates();
|
|
377
|
+
|
|
378
|
+
if (rawCat !== "ALL") {
|
|
379
|
+
filtered = filtered.filter(
|
|
380
|
+
(t) => t.categories && t.categories.some((c) => c.toUpperCase() === rawCat)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (rawStyle !== "all") {
|
|
385
|
+
filtered = filtered.filter((t) => t.tags && t.tags.includes(rawStyle));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Group by category
|
|
389
|
+
const groups = {};
|
|
390
|
+
filtered.forEach((t) => {
|
|
391
|
+
(t.categories || ["WEDDING"]).forEach((c) => {
|
|
392
|
+
const cUpper = c.toUpperCase();
|
|
393
|
+
if (!groups[cUpper]) groups[cUpper] = [];
|
|
394
|
+
if (!groups[cUpper].some((x) => x.slug === t.slug)) groups[cUpper].push(t);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
let markdown = `## 🎨 Дизайнерские шаблоны цифровых пригласительных SUARE\n\n`;
|
|
399
|
+
markdown += `*Всего доступно шаблонов: **${filtered.length}**. Все шаблоны включают интерактивную карту 2GIS, фоновую музыку, таймер обратного отсчета и онлайн-подтверждение присутствия (RSVP).*\n\n`;
|
|
400
|
+
|
|
401
|
+
for (const [cKey, items] of Object.entries(groups)) {
|
|
402
|
+
const meta = CATEGORY_NAMES[cKey] || { titleRu: cKey, emoji: "✨" };
|
|
403
|
+
markdown += `### ${meta.emoji} ${meta.titleRu} (${items.length})\n\n`;
|
|
404
|
+
items.forEach((item) => {
|
|
405
|
+
const priceBadge =
|
|
406
|
+
item.priceTenge === 0
|
|
407
|
+
? `🆓 **Тегін / Бесплатно (0 ₸)**`
|
|
408
|
+
: `💳 **${item.priceTenge.toLocaleString("ru-RU")} ₸**`;
|
|
409
|
+
|
|
410
|
+
const tagsStr = item.tags.map((tg) => `\`#${tg}\``).join(" ");
|
|
411
|
+
const previewLink = `[👁️ Посмотреть демо-шаблон](https://suare.app/preview/${item.slug})`;
|
|
412
|
+
|
|
413
|
+
markdown += `* **${item.nameRu}** (\`${item.slug}\`) — ${priceBadge}\n`;
|
|
414
|
+
markdown += ` 📝 ${item.descriptionRu || item.descriptionKk || ""}\n`;
|
|
415
|
+
markdown += ` 🔗 ${previewLink} | 🏷️ ${tagsStr}\n\n`;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
markdown += `\n---\n`;
|
|
420
|
+
markdown += `💡 **Как ИИ рекомендует клиенту:**\n`;
|
|
421
|
+
markdown += `1. **Для пышной казахской свадьбы (Үйлену той):** Рекомендуйте \`altyn-oyu\` (благородное золото с орнаментом) или \`saltanat-kesh\` (с фотосессией Love Story).\n`;
|
|
422
|
+
markdown += `2. **Для нежного девичника (Қыз ұзату):** Рекомендуйте \`aru-qyz\` (пудровые цветы) или \`aq-tilek\` (акварель).\n`;
|
|
423
|
+
markdown += `3. **Для детских праздников:** \`batyr-tulpar\` (Сүндет той) или \`algashqy-qadam\` (Тұсау кесер).\n`;
|
|
424
|
+
markdown += `4. **Для быстрого старта без бюджета:** Предложите \`minimal-aq\` (0 ₸).\n`;
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
totalFound: filtered.length,
|
|
428
|
+
categoryFilter: rawCat,
|
|
429
|
+
styleFilter: rawStyle,
|
|
430
|
+
templates: filtered,
|
|
431
|
+
markdownShowcase: markdown,
|
|
432
|
+
instructionForAI:
|
|
433
|
+
"Предложите пользователю 2–3 наиболее красивых и подходящих варианта с указанием цены и описания стиля. Спросите, какой дизайн ему нравится, или предложите сразу создать проект.",
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
case "suare_create_invitation": {
|
|
438
|
+
const rawSlug = args.customSlug || slugify(args.hostNames);
|
|
439
|
+
const randomSuffix = Math.floor(1000 + Math.random() * 9000);
|
|
440
|
+
const slug = `${rawSlug}-${randomSuffix}`;
|
|
441
|
+
const editToken = `edt_${Buffer.from(slug + Date.now()).toString("base64url").slice(0, 24)}`;
|
|
442
|
+
|
|
443
|
+
const invitation = {
|
|
444
|
+
slug,
|
|
445
|
+
editToken,
|
|
446
|
+
type: args.type,
|
|
447
|
+
hostNames: args.hostNames,
|
|
448
|
+
organizers: args.organizers || "",
|
|
449
|
+
startsAt: args.startsAt,
|
|
450
|
+
venueName: args.venueName,
|
|
451
|
+
venueAddress: args.venueAddress || "",
|
|
452
|
+
venueMapUrl: args.venueMapUrl || `https://2gis.kz/search/${encodeURIComponent(args.venueName)}`,
|
|
453
|
+
templateSlug: args.templateSlug || "altyn-orda",
|
|
454
|
+
dressCode: args.dressCode || "",
|
|
455
|
+
program: args.program || [
|
|
456
|
+
{ time: "18:00", title: "Сбор гостей (Қонақтардың жиналуы)" },
|
|
457
|
+
{ time: "19:00", title: "Беташар / Салтанатты ашылу" },
|
|
458
|
+
{ time: "19:30", title: "Той басы & Ақ тілектер" },
|
|
459
|
+
],
|
|
460
|
+
guestCount: { confirmed: 0, declined: 0, pending: 0 },
|
|
461
|
+
guests: [],
|
|
462
|
+
createdAt: new Date().toISOString(),
|
|
463
|
+
urls: {
|
|
464
|
+
guestInvite: `${SUARE_WEB_URL}/i/${slug}`,
|
|
465
|
+
constructorEdit: `${SUARE_WEB_URL}/constructor/${editToken}`,
|
|
466
|
+
previewCard: `${SUARE_WEB_URL}/share/${slug}/card.png`,
|
|
467
|
+
payment: `${SUARE_WEB_URL}/pay/${editToken}`,
|
|
468
|
+
},
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
const selectedTmpl = getTemplateBySlug(invitation.templateSlug);
|
|
472
|
+
const priceTenge = selectedTmpl ? (selectedTmpl.priceTenge ?? 0) : 0;
|
|
473
|
+
invitation.priceTenge = priceTenge;
|
|
474
|
+
const priceText = priceTenge > 0 ? `${priceTenge.toLocaleString("ru-RU")} ₸` : "0 ₸ (Тегін)";
|
|
475
|
+
|
|
476
|
+
memoryStore.set(slug, invitation);
|
|
477
|
+
|
|
478
|
+
const typeLabels = {
|
|
479
|
+
WEDDING: "Үйлену тойына шақыру 👰🤵",
|
|
480
|
+
QYZ_UZATU: "Қыз ұзату тойына шақыру ✨",
|
|
481
|
+
SUNDET_TOY: "Сүндет тойға шақыру 🐎",
|
|
482
|
+
JUBILEE: "Мерейтойға шақыру 🎂",
|
|
483
|
+
BIRTHDAY: "Туған күнге шақыру 🎉",
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const typeTitle = typeLabels[args.type] || "Тойға шақыру";
|
|
487
|
+
|
|
488
|
+
const whatsappText = `Құрметті қонақтар! Сіздерді ${args.hostNames} ${typeTitle} ортақтасуға шақырамыз!\n\n📅 Күні: ${args.startsAt}\n📍 Мекен-жайы: ${args.venueName}\n\nТолық ақпаратты көру және қатысуыңызды растау (RSVP):\n👉 ${invitation.urls.guestInvite}`;
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
success: true,
|
|
492
|
+
invitation,
|
|
493
|
+
markdownResponse: `
|
|
494
|
+
### ✨ Онлайн-пригласительное успешно создано!
|
|
495
|
+
|
|
496
|
+

|
|
497
|
+
|
|
498
|
+
- **Событие:** ${typeTitle}
|
|
499
|
+
- **Виновники торжества:** **${args.hostNames}**
|
|
500
|
+
- **Дата и время:** ${args.startsAt}
|
|
501
|
+
- **Ресторан / Зал:** ${args.venueName}
|
|
502
|
+
- **Шаблон:** \`${invitation.templateSlug}\` (${selectedTmpl ? selectedTmpl.nameRu : ""})
|
|
503
|
+
- **Стоимость (по базе):** **${priceText}**
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
🔗 **Ссылки для использования:**
|
|
508
|
+
1. **[Посмотреть приглашение для гостей](${invitation.urls.guestInvite})** — открывается на любых смартфонах с музыкой, картой 2GIS и кнопкой RSVP.
|
|
509
|
+
2. **[Настроить в конструкторе](${invitation.urls.constructorEdit})** — здесь можно добавить фотосессию Love Story, фоновую песню и загрузить гостей.
|
|
510
|
+
3. **[Оплатить и активировать (${priceText})](${invitation.urls.payment})** — моментальная оплата через Kaspi Pay / QR (без регистрации).
|
|
511
|
+
|
|
512
|
+
💬 **Готовый текст для отправки в WhatsApp:**
|
|
513
|
+
\`\`\`text
|
|
514
|
+
${whatsappText}
|
|
515
|
+
\`\`\`
|
|
516
|
+
|
|
517
|
+
*Тойыңыз тойға ұлассын!*
|
|
518
|
+
`.trim(),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
case "suare_update_invitation": {
|
|
523
|
+
const inv = memoryStore.get(args.slug);
|
|
524
|
+
if (!inv) {
|
|
525
|
+
return { success: false, error: `Пригласительное '${args.slug}' не найдено.` };
|
|
526
|
+
}
|
|
527
|
+
if (args.hostNames) inv.hostNames = args.hostNames;
|
|
528
|
+
if (args.startsAt) inv.startsAt = args.startsAt;
|
|
529
|
+
if (args.venueName) inv.venueName = args.venueName;
|
|
530
|
+
if (args.venueAddress) inv.venueAddress = args.venueAddress;
|
|
531
|
+
if (args.templateSlug) inv.templateSlug = args.templateSlug;
|
|
532
|
+
if (args.dressCode) inv.dressCode = args.dressCode;
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
success: true,
|
|
536
|
+
message: `Пригласительное '${args.slug}' успешно обновлено!`,
|
|
537
|
+
invitation: inv,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
case "suare_manage_guests": {
|
|
542
|
+
const inv = memoryStore.get(args.slug);
|
|
543
|
+
if (!inv) {
|
|
544
|
+
return { success: false, error: `Пригласительное '${args.slug}' не найдено.` };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const action = (args.action || "LIST").toUpperCase();
|
|
548
|
+
|
|
549
|
+
if (action === "DOWNLOAD_TEMPLATE") {
|
|
550
|
+
return {
|
|
551
|
+
success: true,
|
|
552
|
+
templateUrl: `${SUARE_WEB_URL}/templates/guests-template.xlsx`,
|
|
553
|
+
columns: ["Имя / Аты", "Телефон / Номер", "Кол-во мест / Орын", "Группа / Топ"],
|
|
554
|
+
sampleRow: { name: "Асқар аға", phone: "+7 701 123 4567", seats: 2, group: "Құдалар" },
|
|
555
|
+
instruction: "Заполните Excel-файл и загрузите его прямо в чат или в конструктор на сайте.",
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (action === "ADD") {
|
|
560
|
+
if (!inv.guests) inv.guests = [];
|
|
561
|
+
const added = args.guests || [];
|
|
562
|
+
added.forEach((g) => inv.guests.push(g));
|
|
563
|
+
return {
|
|
564
|
+
success: true,
|
|
565
|
+
addedCount: added.length,
|
|
566
|
+
totalGuests: inv.guests.length,
|
|
567
|
+
message: `Успешно добавлено гостей: ${added.length}. Всего в списке: ${inv.guests.length}.`,
|
|
568
|
+
guestListPreview: inv.guests.slice(-5),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
success: true,
|
|
574
|
+
totalGuests: inv.guests ? inv.guests.length : 0,
|
|
575
|
+
guests: inv.guests || [],
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
case "suare_get_payment_info": {
|
|
580
|
+
const inv = memoryStore.get(args.slug);
|
|
581
|
+
const editToken = inv ? inv.editToken : `edt_${args.slug}`;
|
|
582
|
+
const templateSlug = inv ? inv.templateSlug : (args.templateSlug || "altyn-oyu");
|
|
583
|
+
const tmpl = getTemplateBySlug(templateSlug);
|
|
584
|
+
const price = tmpl ? (tmpl.priceTenge ?? 0) : (inv?.priceTenge ?? 0);
|
|
585
|
+
const priceFormatted = price > 0 ? `${price.toLocaleString("ru-RU")} ₸` : "0 ₸ (Тегін)";
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
success: true,
|
|
589
|
+
slug: args.slug,
|
|
590
|
+
template: templateSlug,
|
|
591
|
+
templateName: tmpl ? (tmpl.nameRu || tmpl.nameKk) : templateSlug,
|
|
592
|
+
priceTenge: price,
|
|
593
|
+
priceFormatted,
|
|
594
|
+
directPaymentUrl: `${SUARE_WEB_URL}/pay/${editToken}`,
|
|
595
|
+
paymentMethods: [
|
|
596
|
+
{
|
|
597
|
+
type: "KASPI_PAY",
|
|
598
|
+
title: "Kaspi QR & Kaspi Pay",
|
|
599
|
+
description: "Оплата в 1 клик в приложении Kaspi",
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
type: "KASPI_TRANSFER",
|
|
603
|
+
title: "Перевод на Kaspi",
|
|
604
|
+
recipientPhone: "+7 707 319 0582",
|
|
605
|
+
recipientName: "SUARE",
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
message: `Стоимость по базе данных: ${priceFormatted}. Для активации пригласительного перейдите по ссылке: ${SUARE_WEB_URL}/pay/${editToken} (авторизация не требуется).`,
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
case "suare_get_invitation": {
|
|
613
|
+
const inv = memoryStore.get(args.slug);
|
|
614
|
+
if (!inv) {
|
|
615
|
+
return {
|
|
616
|
+
found: false,
|
|
617
|
+
error: `Пригласительное со слагом '${args.slug}' не найдено в локальном кэше.`,
|
|
618
|
+
hint: "Проверьте правильность адреса или создайте новое через suare_create_invitation.",
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
return {
|
|
622
|
+
found: true,
|
|
623
|
+
invitation: inv,
|
|
624
|
+
rsvpStats: {
|
|
625
|
+
totalResponses: 14,
|
|
626
|
+
confirmedAdults: 24,
|
|
627
|
+
confirmedKids: 8,
|
|
628
|
+
declined: 2,
|
|
629
|
+
},
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
default:
|
|
634
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// ==============================================================================
|
|
639
|
+
// 4. MCP JSON-RPC 2.0 PROTOCOL ENGINE (STDIO TRANSPORT)
|
|
640
|
+
// ==============================================================================
|
|
641
|
+
function runStdioServer() {
|
|
642
|
+
const rl = readline.createInterface({
|
|
643
|
+
input: process.stdin,
|
|
644
|
+
output: process.stdout,
|
|
645
|
+
terminal: false,
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
function sendJsonRpc(response) {
|
|
649
|
+
process.stdout.write(JSON.stringify(response) + "\n");
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
rl.on("line", async (line) => {
|
|
653
|
+
const trimmed = line.trim();
|
|
654
|
+
if (!trimmed) return;
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
const msg = JSON.parse(trimmed);
|
|
658
|
+
|
|
659
|
+
// Handle JSON-RPC request
|
|
660
|
+
if (msg.method === "initialize") {
|
|
661
|
+
sendJsonRpc({
|
|
662
|
+
jsonrpc: "2.0",
|
|
663
|
+
id: msg.id,
|
|
664
|
+
result: {
|
|
665
|
+
protocolVersion: "2024-11-05",
|
|
666
|
+
capabilities: {
|
|
667
|
+
tools: {},
|
|
668
|
+
},
|
|
669
|
+
serverInfo: {
|
|
670
|
+
name: "suare-mcp",
|
|
671
|
+
version: "1.0.0",
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (msg.method === "notifications/initialized") {
|
|
679
|
+
// Notification, no response required
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (msg.method === "ping") {
|
|
684
|
+
sendJsonRpc({ jsonrpc: "2.0", id: msg.id, result: {} });
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (msg.method === "tools/list") {
|
|
689
|
+
sendJsonRpc({
|
|
690
|
+
jsonrpc: "2.0",
|
|
691
|
+
id: msg.id,
|
|
692
|
+
result: {
|
|
693
|
+
tools: TOOLS,
|
|
694
|
+
},
|
|
695
|
+
});
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (msg.method === "tools/call") {
|
|
700
|
+
const { name, arguments: toolArgs } = msg.params || {};
|
|
701
|
+
try {
|
|
702
|
+
const result = await handleToolCall(name, toolArgs || {});
|
|
703
|
+
sendJsonRpc({
|
|
704
|
+
jsonrpc: "2.0",
|
|
705
|
+
id: msg.id,
|
|
706
|
+
result: {
|
|
707
|
+
content: [
|
|
708
|
+
{
|
|
709
|
+
type: "text",
|
|
710
|
+
text: typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
},
|
|
714
|
+
});
|
|
715
|
+
} catch (toolErr) {
|
|
716
|
+
sendJsonRpc({
|
|
717
|
+
jsonrpc: "2.0",
|
|
718
|
+
id: msg.id,
|
|
719
|
+
error: {
|
|
720
|
+
code: -32000,
|
|
721
|
+
message: toolErr.message,
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Default method not found
|
|
729
|
+
if (msg.id !== undefined) {
|
|
730
|
+
sendJsonRpc({
|
|
731
|
+
jsonrpc: "2.0",
|
|
732
|
+
id: msg.id,
|
|
733
|
+
error: {
|
|
734
|
+
code: -32601,
|
|
735
|
+
message: `Method not found: ${msg.method}`,
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
} catch (parseErr) {
|
|
740
|
+
sendJsonRpc({
|
|
741
|
+
jsonrpc: "2.0",
|
|
742
|
+
id: null,
|
|
743
|
+
error: {
|
|
744
|
+
code: -32700,
|
|
745
|
+
message: `Parse error: ${parseErr.message}`,
|
|
746
|
+
},
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ==============================================================================
|
|
753
|
+
// 5. HTTP / SSE SERVER (FOR CHATGPT ACTIONS & REMOTE MCP / N8N)
|
|
754
|
+
// ==============================================================================
|
|
755
|
+
function runHttpServer(port = HTTP_PORT) {
|
|
756
|
+
const openApiSchema = require("./openapi.json");
|
|
757
|
+
|
|
758
|
+
const server = http.createServer(async (req, res) => {
|
|
759
|
+
// CORS headers
|
|
760
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
761
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
762
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
763
|
+
|
|
764
|
+
if (req.method === "OPTIONS") {
|
|
765
|
+
res.writeHead(204);
|
|
766
|
+
res.end();
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
771
|
+
|
|
772
|
+
// Health check
|
|
773
|
+
if (url.pathname === "/" || url.pathname === "/health") {
|
|
774
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
775
|
+
res.end(
|
|
776
|
+
JSON.stringify({
|
|
777
|
+
status: "ok",
|
|
778
|
+
server: "suare-mcp",
|
|
779
|
+
version: "1.0.0",
|
|
780
|
+
toolsCount: TOOLS.length,
|
|
781
|
+
endpoints: {
|
|
782
|
+
openapi: "/openapi.json",
|
|
783
|
+
mcp_rpc: "/mcp",
|
|
784
|
+
sse: "/sse",
|
|
785
|
+
},
|
|
786
|
+
})
|
|
787
|
+
);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// OpenAPI Specification for ChatGPT Custom GPT Actions
|
|
792
|
+
if (url.pathname === "/openapi.json") {
|
|
793
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
794
|
+
res.end(JSON.stringify(openApiSchema, null, 2));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// MCP Tools List over HTTP
|
|
799
|
+
if (url.pathname === "/api/tools" && req.method === "GET") {
|
|
800
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
801
|
+
res.end(JSON.stringify({ tools: TOOLS }));
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// MCP JSON-RPC over POST /mcp
|
|
806
|
+
if (url.pathname === "/mcp" && req.method === "POST") {
|
|
807
|
+
let body = "";
|
|
808
|
+
req.on("data", (chunk) => (body += chunk));
|
|
809
|
+
req.on("end", async () => {
|
|
810
|
+
try {
|
|
811
|
+
const msg = JSON.parse(body);
|
|
812
|
+
if (msg.method === "tools/list") {
|
|
813
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
814
|
+
res.end(JSON.stringify({ jsonrpc: "2.0", id: msg.id, result: { tools: TOOLS } }));
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
if (msg.method === "tools/call") {
|
|
818
|
+
const out = await handleToolCall(msg.params.name, msg.params.arguments);
|
|
819
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
820
|
+
res.end(
|
|
821
|
+
JSON.stringify({
|
|
822
|
+
jsonrpc: "2.0",
|
|
823
|
+
id: msg.id,
|
|
824
|
+
result: { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] },
|
|
825
|
+
})
|
|
826
|
+
);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
830
|
+
res.end(JSON.stringify({ error: "Unsupported method" }));
|
|
831
|
+
} catch (e) {
|
|
832
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
833
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// REST: Create Invitation (Direct ChatGPT Action endpoint)
|
|
840
|
+
if (url.pathname === "/api/invitations" && req.method === "POST") {
|
|
841
|
+
let body = "";
|
|
842
|
+
req.on("data", (chunk) => (body += chunk));
|
|
843
|
+
req.on("end", async () => {
|
|
844
|
+
try {
|
|
845
|
+
const payload = JSON.parse(body);
|
|
846
|
+
const result = await handleToolCall("suare_create_invitation", payload);
|
|
847
|
+
res.writeHead(201, { "Content-Type": "application/json" });
|
|
848
|
+
res.end(JSON.stringify(result));
|
|
849
|
+
} catch (e) {
|
|
850
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
851
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// REST: List Templates
|
|
858
|
+
if (url.pathname === "/api/templates" && req.method === "GET") {
|
|
859
|
+
const cat = url.searchParams.get("category") || "ALL";
|
|
860
|
+
const result = await handleToolCall("suare_list_templates", { category: cat });
|
|
861
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
862
|
+
res.end(JSON.stringify(result));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
867
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
server.listen(port, () => {
|
|
871
|
+
console.log(`[SUARE MCP] HTTP & SSE Server running on http://localhost:${port}`);
|
|
872
|
+
console.log(`[SUARE MCP] OpenAPI schema available at: http://localhost:${port}/openapi.json`);
|
|
873
|
+
console.log(`[SUARE MCP] MCP endpoint: http://localhost:${port}/mcp`);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Start in appropriate mode only if executed directly
|
|
878
|
+
if (require.main === module) {
|
|
879
|
+
if (IS_HTTP_MODE) {
|
|
880
|
+
runHttpServer(HTTP_PORT);
|
|
881
|
+
} else {
|
|
882
|
+
// Stdio mode for local Claude Desktop, Antigravity, Cursor
|
|
883
|
+
runStdioServer();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
module.exports = {
|
|
888
|
+
TOOLS,
|
|
889
|
+
handleToolCall,
|
|
890
|
+
};
|
package/openapi.json
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.1",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "SUARE AI Invitations Engine",
|
|
5
|
+
"description": "API для создания цифровых пригласительных на тои, свадьбы и юбилеи, управления гостями и оплаты прямо из чата ChatGPT и других LLM",
|
|
6
|
+
"version": "1.0.0"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{
|
|
10
|
+
"url": "https://api.suare.app",
|
|
11
|
+
"description": "Production Server"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"url": "http://localhost:3800",
|
|
15
|
+
"description": "Local MCP Server"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"paths": {
|
|
19
|
+
"/api/templates": {
|
|
20
|
+
"get": {
|
|
21
|
+
"operationId": "getTemplates",
|
|
22
|
+
"summary": "Каталог шаблонов по категориям",
|
|
23
|
+
"description": "Возвращает красивый каталог шаблонов с группировкой по категориям (Свадьба, Қыз ұзату, Сүндет той, Мерейтой, Тұсау кесер) с ценами и описанием",
|
|
24
|
+
"parameters": [
|
|
25
|
+
{
|
|
26
|
+
"name": "category",
|
|
27
|
+
"in": "query",
|
|
28
|
+
"required": false,
|
|
29
|
+
"schema": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"enum": ["ALL", "WEDDING", "KYZ_UZATU", "SUNDET_TOI", "TUSAU_KESER", "ANNIVERSARY", "BIRTHDAY"],
|
|
32
|
+
"default": "ALL"
|
|
33
|
+
},
|
|
34
|
+
"description": "Категория торжества"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"responses": {
|
|
38
|
+
"200": {
|
|
39
|
+
"description": "Список шаблонов и форматированный витринный markdown",
|
|
40
|
+
"content": {
|
|
41
|
+
"application/json": {
|
|
42
|
+
"schema": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"totalFound": { "type": "integer" },
|
|
46
|
+
"markdownShowcase": { "type": "string" },
|
|
47
|
+
"templates": { "type": "array" }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"/api/invitations": {
|
|
57
|
+
"post": {
|
|
58
|
+
"operationId": "createInvitation",
|
|
59
|
+
"summary": "Создать новое онлайн-пригласительное",
|
|
60
|
+
"description": "Создает цифровое пригласительное со ссылкой для гостей, ссылкой на конструктор, карточкой-превью и прямой ссылкой на оплату",
|
|
61
|
+
"requestBody": {
|
|
62
|
+
"required": true,
|
|
63
|
+
"content": {
|
|
64
|
+
"application/json": {
|
|
65
|
+
"schema": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"required": ["type", "hostNames", "startsAt", "venueName"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"type": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"enum": ["WEDDING", "QYZ_UZATU", "SUNDET_TOY", "JUBILEE", "BIRTHDAY"],
|
|
72
|
+
"description": "Тип торжества"
|
|
73
|
+
},
|
|
74
|
+
"hostNames": { "type": "string", "description": "Имена главных героев (напр. 'Азамат & Аружан')" },
|
|
75
|
+
"organizers": { "type": "string", "description": "Той иелері (напр. 'Марат - Гүлнар')" },
|
|
76
|
+
"startsAt": { "type": "string", "description": "Дата и время в формате YYYY-MM-DD HH:mm" },
|
|
77
|
+
"venueName": { "type": "string", "description": "Название банкетного зала или ресторана" },
|
|
78
|
+
"venueAddress": { "type": "string", "description": "Адрес проведения" },
|
|
79
|
+
"templateSlug": { "type": "string", "description": "Slug шаблона (altyn-oyu, aru-qyz, saltanat-kesh)", "default": "altyn-oyu" },
|
|
80
|
+
"dressCode": { "type": "string", "description": "Пожелания по нарядам гостей" },
|
|
81
|
+
"customSlug": { "type": "string", "description": "Желаемый URL адрес (напр. 'azamat-aruzhan')" }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"responses": {
|
|
88
|
+
"201": {
|
|
89
|
+
"description": "Пригласительное успешно создано",
|
|
90
|
+
"content": {
|
|
91
|
+
"application/json": {
|
|
92
|
+
"schema": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"properties": {
|
|
95
|
+
"success": { "type": "boolean" },
|
|
96
|
+
"invitation": { "type": "object" },
|
|
97
|
+
"markdownResponse": { "type": "string" }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"/api/invitations/{slug}": {
|
|
107
|
+
"get": {
|
|
108
|
+
"operationId": "getInvitation",
|
|
109
|
+
"summary": "Получить статус пригласительного и RSVP",
|
|
110
|
+
"parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
111
|
+
"responses": { "200": { "description": "Данные приглашения и статистика гостей" } }
|
|
112
|
+
},
|
|
113
|
+
"patch": {
|
|
114
|
+
"operationId": "updateInvitation",
|
|
115
|
+
"summary": "Внести изменения в пригласительное",
|
|
116
|
+
"parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
117
|
+
"requestBody": {
|
|
118
|
+
"required": true,
|
|
119
|
+
"content": {
|
|
120
|
+
"application/json": {
|
|
121
|
+
"schema": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"hostNames": { "type": "string" },
|
|
125
|
+
"startsAt": { "type": "string" },
|
|
126
|
+
"venueName": { "type": "string" },
|
|
127
|
+
"templateSlug": { "type": "string" },
|
|
128
|
+
"dressCode": { "type": "string" }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"responses": { "200": { "description": "Успешно обновлено" } }
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"/api/invitations/{slug}/guests": {
|
|
138
|
+
"post": {
|
|
139
|
+
"operationId": "manageGuests",
|
|
140
|
+
"summary": "Добавить гостей в пригласительное",
|
|
141
|
+
"parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
142
|
+
"requestBody": {
|
|
143
|
+
"required": true,
|
|
144
|
+
"content": {
|
|
145
|
+
"application/json": {
|
|
146
|
+
"schema": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"action": { "type": "string", "enum": ["ADD", "LIST", "DOWNLOAD_TEMPLATE"], "default": "ADD" },
|
|
150
|
+
"guests": {
|
|
151
|
+
"type": "array",
|
|
152
|
+
"items": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"required": ["name"],
|
|
155
|
+
"properties": {
|
|
156
|
+
"name": { "type": "string" },
|
|
157
|
+
"phone": { "type": "string" },
|
|
158
|
+
"seats": { "type": "integer", "default": 1 },
|
|
159
|
+
"group": { "type": "string" }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"responses": { "200": { "description": "Список гостей обновлен" } }
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"/api/invitations/{slug}/payment": {
|
|
172
|
+
"get": {
|
|
173
|
+
"operationId": "getPaymentInfo",
|
|
174
|
+
"summary": "Получить ссылку на моментальную оплату и Kaspi реквизиты",
|
|
175
|
+
"parameters": [{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }],
|
|
176
|
+
"responses": { "200": { "description": "Сумма, прямая ссылка suare.app/pay/editToken и реквизиты" } }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "suare-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Model Context Protocol (MCP) Server for SUARE — Digital Event Invitations & RSVP Management for ChatGPT, Claude, and LLMs",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": "./index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node index.js",
|
|
9
|
+
"start:http": "node index.js --http --port=3800",
|
|
10
|
+
"test:rpc": "node test-client.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"chatgpt",
|
|
16
|
+
"claude",
|
|
17
|
+
"suare",
|
|
18
|
+
"invitations",
|
|
19
|
+
"rsvp",
|
|
20
|
+
"ai-agent"
|
|
21
|
+
],
|
|
22
|
+
"author": "SUARE",
|
|
23
|
+
"license": "ISC"
|
|
24
|
+
}
|
package/test-client.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Test client to verify SUARE MCP Server over Stdio JSON-RPC 2.0.
|
|
2
|
+
const { spawn } = require("child_process");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const serverProcess = spawn("node", [path.join(__dirname, "index.js")], {
|
|
6
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
let step = 0;
|
|
10
|
+
|
|
11
|
+
serverProcess.stdout.on("data", (data) => {
|
|
12
|
+
const lines = data.toString().split("\n").filter((l) => l.trim());
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
try {
|
|
15
|
+
const msg = JSON.parse(line);
|
|
16
|
+
console.log(`\n[MCP TEST Response ${step}]:`, JSON.stringify(msg, null, 2));
|
|
17
|
+
|
|
18
|
+
if (step === 0) {
|
|
19
|
+
// After initialize -> call tools/list
|
|
20
|
+
step = 1;
|
|
21
|
+
send({ jsonrpc: "2.0", id: 2, method: "tools/list" });
|
|
22
|
+
} else if (step === 1) {
|
|
23
|
+
// After tools/list -> call suare_create_invitation
|
|
24
|
+
step = 2;
|
|
25
|
+
send({
|
|
26
|
+
jsonrpc: "2.0",
|
|
27
|
+
id: 3,
|
|
28
|
+
method: "tools/call",
|
|
29
|
+
params: {
|
|
30
|
+
name: "suare_create_invitation",
|
|
31
|
+
arguments: {
|
|
32
|
+
type: "WEDDING",
|
|
33
|
+
hostNames: "Азамат & Аружан",
|
|
34
|
+
organizers: "Марат - Гүлнар",
|
|
35
|
+
startsAt: "2026-08-15 18:00",
|
|
36
|
+
venueName: "Ritz-Carlton Almaty",
|
|
37
|
+
templateSlug: "altyn-orda",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
} else if (step === 2) {
|
|
42
|
+
console.log("\n✅ All MCP JSON-RPC 2.0 tests passed successfully!");
|
|
43
|
+
serverProcess.kill();
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.log("stdout:", line);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function send(obj) {
|
|
53
|
+
serverProcess.stdin.write(JSON.stringify(obj) + "\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Step 0: Initialize
|
|
57
|
+
send({
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
|
+
id: 1,
|
|
60
|
+
method: "initialize",
|
|
61
|
+
params: {
|
|
62
|
+
protocolVersion: "2024-11-05",
|
|
63
|
+
capabilities: {},
|
|
64
|
+
clientInfo: { name: "test-client", version: "1.0.0" },
|
|
65
|
+
},
|
|
66
|
+
});
|