schemeog-mcp 2.2.1 → 2.3.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.
Files changed (2) hide show
  1. package/index.js +248 -18
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -209,15 +209,208 @@ function generateId() {
209
209
  }
210
210
 
211
211
  /**
212
- * Подготовить элементы добавить ID если нет
212
+ * Допустимые именованные цвета для элементов
213
+ */
214
+ const VALID_COLORS = ["white", "light_blue", "light_green", "light_yellow", "light_orange", "light_red", "light_purple", "light_gray", "light_pink", "light_teal"];
215
+
216
+ /**
217
+ * Маппинг HEX цветов к именованным (ближайшее соответствие)
218
+ */
219
+ const HEX_TO_NAMED_COLOR = {
220
+ // Белые/серые
221
+ "#FFFFFF": "white", "#FFF": "white", "#FAFAFA": "white", "#F5F5F5": "light_gray",
222
+ "#E0E0E0": "light_gray", "#BDBDBD": "light_gray", "#9E9E9E": "light_gray",
223
+ "#CFD8DC": "light_gray", "#ECEFF1": "light_gray",
224
+
225
+ // Синие
226
+ "#E3F2FD": "light_blue", "#BBDEFB": "light_blue", "#90CAF9": "light_blue",
227
+ "#64B5F6": "light_blue", "#42A5F5": "light_blue", "#2196F3": "light_blue",
228
+ "#1E88E5": "light_blue", "#1976D2": "light_blue",
229
+
230
+ // Зелёные
231
+ "#E8F5E9": "light_green", "#C8E6C9": "light_green", "#A5D6A7": "light_green",
232
+ "#81C784": "light_green", "#66BB6A": "light_green", "#4CAF50": "light_green",
233
+ "#43A047": "light_green", "#388E3C": "light_green", "#DCEDC8": "light_green",
234
+
235
+ // Жёлтые
236
+ "#FFFDE7": "light_yellow", "#FFF9C4": "light_yellow", "#FFF59D": "light_yellow",
237
+ "#FFF176": "light_yellow", "#FFEE58": "light_yellow", "#FFEB3B": "light_yellow",
238
+ "#FDD835": "light_yellow", "#FBC02D": "light_yellow",
239
+
240
+ // Оранжевые
241
+ "#FFF3E0": "light_orange", "#FFE0B2": "light_orange", "#FFCC80": "light_orange",
242
+ "#FFB74D": "light_orange", "#FFA726": "light_orange", "#FF9800": "light_orange",
243
+ "#FB8C00": "light_orange", "#F57C00": "light_orange",
244
+ "#FFE5B4": "light_orange", "#FFCCBC": "light_orange", // персиковые
245
+
246
+ // Красные
247
+ "#FFEBEE": "light_red", "#FFCDD2": "light_red", "#EF9A9A": "light_red",
248
+ "#E57373": "light_red", "#EF5350": "light_red", "#F44336": "light_red",
249
+ "#E53935": "light_red", "#D32F2F": "light_red",
250
+ "#FCE4EC": "light_pink", "#F8BBD0": "light_pink", // розовые → light_pink
251
+
252
+ // Фиолетовые
253
+ "#F3E5F5": "light_purple", "#E1BEE7": "light_purple", "#CE93D8": "light_purple",
254
+ "#BA68C8": "light_purple", "#AB47BC": "light_purple", "#9C27B0": "light_purple",
255
+ "#8E24AA": "light_purple", "#7B1FA2": "light_purple",
256
+ "#D1C4E9": "light_purple", "#EDE7F6": "light_purple",
257
+
258
+ // Розовые
259
+ "#FCE4EC": "light_pink", "#F8BBD0": "light_pink", "#F48FB1": "light_pink",
260
+ "#F06292": "light_pink", "#EC407A": "light_pink", "#E91E63": "light_pink",
261
+
262
+ // Бирюзовые/Teal
263
+ "#E0F7FA": "light_teal", "#B2DFDB": "light_teal", "#80CBC4": "light_teal",
264
+ "#4DB6AC": "light_teal", "#26A69A": "light_teal", "#009688": "light_teal",
265
+ "#E0F2F1": "light_teal",
266
+ };
267
+
268
+ /**
269
+ * Конвертировать HEX цвет в ближайший именованный
270
+ */
271
+ function hexToNamedColor(hex) {
272
+ if (!hex) return "white";
273
+
274
+ // Если уже именованный — проверяем и возвращаем
275
+ if (VALID_COLORS.includes(hex)) return hex;
276
+
277
+ // Нормализуем HEX
278
+ const normalizedHex = hex.toUpperCase().trim();
279
+
280
+ // Ищем точное соответствие
281
+ if (HEX_TO_NAMED_COLOR[normalizedHex]) {
282
+ return HEX_TO_NAMED_COLOR[normalizedHex];
283
+ }
284
+
285
+ // Если не нашли — пытаемся определить по оттенку
286
+ // Простой алгоритм: анализируем RGB компоненты
287
+ const match = normalizedHex.match(/^#?([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})$/i);
288
+ if (match) {
289
+ const r = parseInt(match[1], 16);
290
+ const g = parseInt(match[2], 16);
291
+ const b = parseInt(match[3], 16);
292
+
293
+ // Определяем доминирующий цвет
294
+ const max = Math.max(r, g, b);
295
+ const min = Math.min(r, g, b);
296
+ const brightness = (r + g + b) / 3;
297
+
298
+ // Белый/серый
299
+ if (max - min < 30) {
300
+ return brightness > 200 ? "white" : "light_gray";
301
+ }
302
+
303
+ // Красный доминирует
304
+ if (r === max && r > g + 20 && r > b + 20) {
305
+ return g > 150 ? "light_orange" : (b > 100 ? "light_pink" : "light_red");
306
+ }
307
+
308
+ // Зелёный доминирует
309
+ if (g === max && g > r + 20 && g > b + 20) {
310
+ return b > 150 ? "light_teal" : "light_green";
311
+ }
312
+
313
+ // Синий доминирует
314
+ if (b === max && b > r + 20 && b > g + 20) {
315
+ return r > 150 ? "light_purple" : "light_blue";
316
+ }
317
+
318
+ // Жёлтый (R и G высокие, B низкий)
319
+ if (r > 200 && g > 200 && b < 150) {
320
+ return "light_yellow";
321
+ }
322
+
323
+ // Оранжевый (R высокий, G средний, B низкий)
324
+ if (r > 200 && g > 100 && g < 200 && b < 150) {
325
+ return "light_orange";
326
+ }
327
+ }
328
+
329
+ // Fallback
330
+ return "light_blue";
331
+ }
332
+
333
+ /**
334
+ * Нормализовать элементы — исправить типы, цвета, удалить лишние поля
335
+ * Возвращает { elements, warnings }
336
+ */
337
+ function normalizeElements(elements) {
338
+ if (!elements || !Array.isArray(elements)) return { elements: [], warnings: [] };
339
+
340
+ const warnings = [];
341
+
342
+ const normalized = elements.map((el, index) => {
343
+ const fixed = {
344
+ id: el.id || generateId(),
345
+ };
346
+
347
+ // Тип: block/note → card
348
+ if (el.type === "block" || el.type === "note" || el.type === "group") {
349
+ fixed.type = "card";
350
+ warnings.push(`Элемент ${index + 1}: type "${el.type}" → "card"`);
351
+ } else if (el.type === "condition" || el.type === "diamond") {
352
+ fixed.type = "condition";
353
+ } else {
354
+ fixed.type = el.type || "card";
355
+ }
356
+
357
+ // Title: text → title
358
+ if (el.text && !el.title) {
359
+ fixed.title = el.text;
360
+ warnings.push(`Элемент ${index + 1}: "text" → "title"`);
361
+ } else {
362
+ fixed.title = el.title || "";
363
+ }
364
+
365
+ // Цвет: HEX → именованный
366
+ if (el.color) {
367
+ if (el.color.startsWith("#")) {
368
+ const namedColor = hexToNamedColor(el.color);
369
+ fixed.color = namedColor;
370
+ warnings.push(`Элемент ${index + 1}: color "${el.color}" → "${namedColor}"`);
371
+ } else if (VALID_COLORS.includes(el.color)) {
372
+ fixed.color = el.color;
373
+ } else {
374
+ fixed.color = "white";
375
+ warnings.push(`Элемент ${index + 1}: неизвестный color "${el.color}" → "white"`);
376
+ }
377
+ } else {
378
+ fixed.color = "white";
379
+ }
380
+
381
+ // borderColor — оставляем если валидный
382
+ const validBorderColors = ["default", "blue", "green", "orange", "purple", "red", "teal", "yellow", "gray", "black"];
383
+ if (el.borderColor && validBorderColors.includes(el.borderColor)) {
384
+ fixed.borderColor = el.borderColor;
385
+ }
386
+
387
+ // Description
388
+ if (el.description) {
389
+ fixed.description = el.description;
390
+ }
391
+
392
+ // Tags
393
+ if (el.tags && Array.isArray(el.tags)) {
394
+ fixed.tags = el.tags;
395
+ }
396
+
397
+ // НЕ копируем: x, y, width, height — они автоматические
398
+ if (el.x !== undefined || el.y !== undefined || el.width !== undefined || el.height !== undefined) {
399
+ warnings.push(`Элемент ${index + 1}: x/y/width/height удалены (рассчитываются автоматически)`);
400
+ }
401
+
402
+ return fixed;
403
+ });
404
+
405
+ return { elements: normalized, warnings };
406
+ }
407
+
408
+ /**
409
+ * Подготовить элементы — нормализовать и добавить ID
410
+ * Возвращает { elements, warnings }
213
411
  */
214
412
  function prepareElements(elements) {
215
- if (!elements) return [];
216
- return elements.map(el => ({
217
- id: el.id || generateId(),
218
- type: el.type || "card",
219
- ...el
220
- }));
413
+ return normalizeElements(elements);
221
414
  }
222
415
 
223
416
  /**
@@ -256,11 +449,22 @@ const TOOLS = [
256
449
  Возвращает:
257
450
  - name: название схемы
258
451
  - description: описание
259
- - elements: массив элементов (блоки, заметки, группы)
452
+ - elements: массив элементов (card или condition)
260
453
  - connections: массив связей между элементами
454
+ - tagsDictionary: справочник тегов
455
+
456
+ Формат элемента:
457
+ {
458
+ "id": "unique_id",
459
+ "type": "card", // card или condition
460
+ "title": "Название", // заголовок
461
+ "color": "light_blue", // ТОЛЬКО: white, light_blue, light_green, light_yellow, light_orange, light_red, light_purple, light_gray, light_pink, light_teal
462
+ "description": "Текст", // описание
463
+ "tags": ["тег1"] // массив тегов
464
+ }
261
465
 
262
- Каждый элемент содержит: id, type, x, y, text, width, height, color
263
- Каждая связь содержит: id, from, to, label, style`,
466
+ ВАЖНО: НЕ используй x, y, width, height — они рассчитываются автоматически!
467
+ ВАЖНО: Цвета ТОЛЬКО именованные (light_blue), НЕ HEX (#FFFFFF)!`,
264
468
  inputSchema: {
265
469
  type: "object",
266
470
  properties: {
@@ -834,12 +1038,15 @@ ${JSON.stringify(schema.data?.connections || [], null, 2)}`,
834
1038
  }
835
1039
 
836
1040
  case "create_schema": {
837
- const { name, description, project_id, elements, connections } = args;
1041
+ const { name, description, project_id, elements, connections, tagsDictionary } = args;
1042
+
1043
+ // Нормализуем элементы и получаем предупреждения
1044
+ const { elements: normalizedElements, warnings } = prepareElements(elements);
838
1045
 
839
1046
  const data = {
840
- elements: prepareElements(elements),
1047
+ elements: normalizedElements,
841
1048
  connections: prepareConnections(connections),
842
- tagsDictionary: [],
1049
+ tagsDictionary: tagsDictionary || [],
843
1050
  };
844
1051
 
845
1052
  const result = await apiRequest("/schemas", {
@@ -849,6 +1056,16 @@ ${JSON.stringify(schema.data?.connections || [], null, 2)}`,
849
1056
 
850
1057
  const projectInfo = project_id ? `Проект: ${project_id}` : 'Проект: Общие';
851
1058
 
1059
+ // Формируем сообщение о предупреждениях
1060
+ let warningsText = "";
1061
+ if (warnings.length > 0) {
1062
+ warningsText = `\n\n⚠️ Автоисправления (${warnings.length}):\n• ${warnings.slice(0, 10).join("\n• ")}`;
1063
+ if (warnings.length > 10) {
1064
+ warningsText += `\n• ...и ещё ${warnings.length - 10}`;
1065
+ }
1066
+ warningsText += `\n\n💡 Подсказка: используй type "card"/"condition", цвета light_blue/light_green/etc, НЕ указывай x/y/width/height`;
1067
+ }
1068
+
852
1069
  return {
853
1070
  content: [{
854
1071
  type: "text",
@@ -861,7 +1078,7 @@ ${projectInfo}
861
1078
 
862
1079
  URL: ${API_BASE_URL}/canvas?id=${result.schema.id}
863
1080
 
864
- Открой ссылку в браузере чтобы увидеть схему.`,
1081
+ Открой ссылку в браузере чтобы увидеть схему.${warningsText}`,
865
1082
  }],
866
1083
  };
867
1084
  }
@@ -876,6 +1093,7 @@ URL: ${API_BASE_URL}/canvas?id=${result.schema.id}
876
1093
 
877
1094
  // Формируем обновление
878
1095
  const updateBody = {};
1096
+ let warnings = [];
879
1097
 
880
1098
  if (name) updateBody.name = name;
881
1099
  if (description !== undefined) updateBody.description = description;
@@ -883,7 +1101,9 @@ URL: ${API_BASE_URL}/canvas?id=${result.schema.id}
883
1101
  // Обновляем данные только если переданы
884
1102
  const newData = { ...currentData };
885
1103
  if (elements !== undefined) {
886
- newData.elements = prepareElements(elements);
1104
+ const normalized = prepareElements(elements);
1105
+ newData.elements = normalized.elements;
1106
+ warnings = normalized.warnings;
887
1107
  }
888
1108
  if (connections !== undefined) {
889
1109
  newData.connections = prepareConnections(connections);
@@ -895,6 +1115,16 @@ URL: ${API_BASE_URL}/canvas?id=${result.schema.id}
895
1115
  body: JSON.stringify(updateBody),
896
1116
  });
897
1117
 
1118
+ // Формируем сообщение о предупреждениях
1119
+ let warningsText = "";
1120
+ if (warnings.length > 0) {
1121
+ warningsText = `\n\n⚠️ Автоисправления (${warnings.length}):\n• ${warnings.slice(0, 10).join("\n• ")}`;
1122
+ if (warnings.length > 10) {
1123
+ warningsText += `\n• ...и ещё ${warnings.length - 10}`;
1124
+ }
1125
+ warningsText += `\n\n💡 Подсказка: используй type "card"/"condition", цвета light_blue/light_green/etc, НЕ указывай x/y/width/height`;
1126
+ }
1127
+
898
1128
  return {
899
1129
  content: [{
900
1130
  type: "text",
@@ -903,7 +1133,7 @@ URL: ${API_BASE_URL}/canvas?id=${result.schema.id}
903
1133
  Элементов: ${result.schema.data?.elements?.length || 0}
904
1134
  Связей: ${result.schema.data?.connections?.length || 0}
905
1135
 
906
- URL: ${API_BASE_URL}/canvas?id=${schema_id}`,
1136
+ URL: ${API_BASE_URL}/canvas?id=${schema_id}${warningsText}`,
907
1137
  }],
908
1138
  };
909
1139
  }
@@ -1208,7 +1438,7 @@ async function main() {
1208
1438
  const server = new Server(
1209
1439
  {
1210
1440
  name: "schemeog-mcp",
1211
- version: "2.2.1",
1441
+ version: "2.3.0",
1212
1442
  },
1213
1443
  {
1214
1444
  capabilities: {
@@ -1238,7 +1468,7 @@ async function main() {
1238
1468
  const transport = new StdioServerTransport();
1239
1469
  await server.connect(transport);
1240
1470
 
1241
- console.error("SchemeOG MCP Server v2.2 запущен");
1471
+ console.error("SchemeOG MCP Server v2.3 запущен");
1242
1472
  }
1243
1473
 
1244
1474
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schemeog-mcp",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "MCP Server for SchemeOG Cloud - Create and manage visual diagrams and flowcharts with AI assistance",
5
5
  "type": "module",
6
6
  "main": "index.js",