beplus-mcp 0.13.0 → 0.16.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/dist/index.js +132 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -215,6 +215,10 @@ var TEAM_GATING_NOTE = " \u26A0\uFE0F DESTINO DA GERA\xC7\xC3O: toda imagem/v\xE
215
215
  function isTeamGatingError(msg) {
216
216
  return !!msg && /cliente e um projeto|selecione um cliente|sem cliente vinculado|antes de gerar/i.test(msg);
217
217
  }
218
+ function isBudgetBlockedError(msg) {
219
+ return !!msg && /budget/i.test(msg);
220
+ }
221
+ var BUDGET_BLOCKED_GUIDANCE = "\u{1F4A1} O pote deste projeto/cliente acabou. Um admin pode recarregar em Admin \u2192 IA Lab \u2192 Projetos/Clientes. Alternativa: trocar o projeto ativo com `set_active_project`.";
218
222
  async function buildPickProjectMessage(client) {
219
223
  let clientsTxt = " (n\xE3o foi poss\xEDvel listar clientes)";
220
224
  let projectsTxt = " (n\xE3o foi poss\xEDvel listar projetos)";
@@ -401,6 +405,16 @@ function weeklyAllowanceLine(wa) {
401
405
  }
402
406
  return line;
403
407
  }
408
+ function budgetLine(b) {
409
+ if (!b || !b.enabled) return "";
410
+ const scope = b.scope === "client" ? "cliente" : "projeto";
411
+ const pct = b.percentUsed;
412
+ const exhausted = pct >= 100 || b.remainingDiamonds <= 0;
413
+ const icon = exhausted ? "\u{1F6D1}" : pct >= 80 ? "\u26A0\uFE0F" : "\u{1F9FE}";
414
+ let line = `${icon} Budget do ${scope}: ${b.remainingDiamonds} de ${b.quotaDiamonds}\u{1F48E} restantes (${pct}% usado)`;
415
+ if (exhausted) line += " \u2014 pe\xE7a a um admin para recarregar";
416
+ return line;
417
+ }
404
418
  function textResult(s) {
405
419
  return { content: [{ type: "text", text: s }] };
406
420
  }
@@ -423,10 +437,16 @@ function costFooter(start, cfg) {
423
437
  footer = `\u26A0 Custo alto (${start.diamond_cost} \u{1F48E})
424
438
  ${footer}`;
425
439
  }
440
+ const budget = budgetLine(start.budget);
426
441
  if (start.project_code) {
427
- const proj = `\u{1F4C1} Projeto: ${start.project_code}${start.project_name ? ` \u2014 ${start.project_name}` : ""}`;
442
+ let proj = `\u{1F4C1} Projeto: ${start.project_code}${start.project_name ? ` \u2014 ${start.project_name}` : ""}`;
443
+ if (budget) proj = `${proj}
444
+ ${budget}`;
428
445
  footer = footer ? `${proj}
429
446
  ${footer}` : proj;
447
+ } else if (budget) {
448
+ footer = footer ? `${budget}
449
+ ${footer}` : budget;
430
450
  }
431
451
  const wa = weeklyAllowanceLine(start.weekly_allowance);
432
452
  if (wa) footer = footer ? `${footer}
@@ -466,8 +486,12 @@ var IMAGE_MODELS = [
466
486
  "google/nano-banana-2",
467
487
  "google/nano-banana-pro",
468
488
  "openai/gpt-image-2",
489
+ "bytedance/seedream-5.0-lite",
490
+ "bytedance/seedream-5.0-pro",
469
491
  "midjourney"
470
492
  ];
493
+ var SEEDREAM_LITE_SIZES = ["2K", "3K", "4K"];
494
+ var SEEDREAM_PRO_SIZES = ["1K", "2K"];
471
495
  var VIDEO_MODELS = [
472
496
  "bytedance/seedance-2.0",
473
497
  "bytedance/seedance-2.0-fast",
@@ -561,8 +585,13 @@ function registerGenerationTools(server, client, cfg) {
561
585
  title: "Gerar imagem",
562
586
  description: "Gera uma imagem a partir de um prompt usando o IA Lab da BePlus. R\xE1pido (~10-30s). Consome diamantes da conta vinculada. Apenas `prompt` \xE9 obrigat\xF3rio; passe `reference_images` (URLs p\xFAblicas OU caminhos de arquivo local \u2014 o MCP sobe pro R2 automaticamente) para image-to-image. Veja custos com estimate_cost / list_models." + TEAM_GATING_NOTE,
563
587
  inputSchema: {
564
- prompt: z.string().min(1).max(4e3).describe("Descri\xE7\xE3o da imagem desejada."),
565
- model: z.enum(IMAGE_MODELS).default(DEFAULTS.imageModel).describe("nano-banana-2 = barato/r\xE1pido; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI."),
588
+ // Sem teto local: quem conhece o limite de cada provedor é o backend, e
589
+ // ele responde com quanto reduzir. Um `.max()` aqui rejeitava prompts
590
+ // que o modelo aceita (Seedream/nano-banana não têm limite publicado).
591
+ prompt: z.string().min(1).describe("Descri\xE7\xE3o da imagem desejada."),
592
+ model: z.enum(IMAGE_MODELS).default(DEFAULTS.imageModel).describe(
593
+ "nano-banana-2 = barato/r\xE1pido; nano-banana-pro = melhor qualidade; gpt-image-2 = OpenAI; seedream-5.0-lite = mais barato do cat\xE1logo, \xFAnico que chega a 4K, gera s\xE9ries e mant\xE9m o ESTILO da imagem de refer\xEAncia; seedream-5.0-pro = acabamento fotorrealista, s\xF3 at\xE9 2K \u2014 preserva o personagem da refer\xEAncia mas costuma reinterpretar o estilo (um vetor flat volta em 3D)."
594
+ ),
566
595
  aspect_ratio: z.enum(ASPECT_RATIOS).optional().describe("Propor\xE7\xE3o (nano-banana). Padr\xE3o 1:1."),
567
596
  resolution: z.enum(IMAGE_RESOLUTIONS).optional().describe("Resolu\xE7\xE3o (nano-banana): 1K/2K/4K. Padr\xE3o 1K."),
568
597
  reference_images: z.array(z.string().min(1)).max(16).optional().describe(
@@ -570,7 +599,12 @@ function registerGenerationTools(server, client, cfg) {
570
599
  ),
571
600
  google_search: z.boolean().optional().describe("nano-banana-2: aterra a imagem em busca web do Google (fatos atuais). Padr\xE3o false."),
572
601
  image_search: z.boolean().optional().describe("nano-banana-2: usa busca de imagens do Google como refer\xEAncia visual. Padr\xE3o false."),
573
- size: z.enum(["1024x1024", "1536x1024", "1024x1536", "auto"]).optional().describe("Tamanho (gpt-image-2)."),
602
+ size: z.enum(["1024x1024", "1536x1024", "1024x1536", "auto", "1K", "2K", "3K", "4K"]).optional().describe(
603
+ "Tamanho. gpt-image-2: 1024x1024/1536x1024/1024x1536/auto. Seedream Lite: 2K/3K/4K (padr\xE3o 2K). Seedream Pro: 1K/2K apenas \u2014 o Pro N\xC3O faz 3K/4K."
604
+ ),
605
+ batch_images: z.number().int().min(2).max(15).optional().describe(
606
+ "Seedream 5.0 LITE apenas: gera uma s\xE9rie de imagens relacionadas numa chamada (2-15). Cada imagem \xE9 cobrada. O Seedream Pro N\xC3O suporta \u2014 passar aqui devolve erro."
607
+ ),
574
608
  quality: z.enum(["low", "medium", "high", "auto"]).optional().describe("Qualidade (gpt-image-2)."),
575
609
  background: z.enum(["opaque", "transparent", "auto"]).optional().describe("Fundo (gpt-image-2). transparent s\xF3 com output_format png/webp. Padr\xE3o opaque."),
576
610
  moderation: z.enum(["low", "auto"]).optional().describe("Modera\xE7\xE3o (gpt-image-2). Padr\xE3o low."),
@@ -599,6 +633,33 @@ function registerGenerationTools(server, client, cfg) {
599
633
  try {
600
634
  const isGoogle = args.model.startsWith("google/");
601
635
  const isMidjourney = args.model === "midjourney";
636
+ const isSeedreamLite = args.model === "bytedance/seedream-5.0-lite";
637
+ const isSeedreamPro = args.model === "bytedance/seedream-5.0-pro";
638
+ const isSeedream = isSeedreamLite || isSeedreamPro;
639
+ if (isSeedream) {
640
+ const allowed = isSeedreamPro ? SEEDREAM_PRO_SIZES : SEEDREAM_LITE_SIZES;
641
+ if (args.size && !allowed.includes(args.size)) {
642
+ return errorResult(
643
+ `${args.model} aceita size ${allowed.join("/")} (voc\xEA pediu ${args.size}). ` + (isSeedreamPro ? "O Pro n\xE3o faz 3K/4K \u2014 use o seedream-5.0-lite para resolu\xE7\xF5es maiores." : "O Lite n\xE3o faz 1K \u2014 use o seedream-5.0-pro se quiser a resolu\xE7\xE3o menor.")
644
+ );
645
+ }
646
+ if (args.batch_images && isSeedreamPro) {
647
+ return errorResult(
648
+ "O seedream-5.0-pro n\xE3o suporta gera\xE7\xE3o em s\xE9rie (batch_images). Use bytedance/seedream-5.0-lite para gerar v\xE1rias imagens relacionadas de uma vez."
649
+ );
650
+ }
651
+ if (args.output_format === "webp") {
652
+ return errorResult(`${args.model} aceita apenas png ou jpeg (webp n\xE3o \xE9 suportado).`);
653
+ }
654
+ const maxRefs = isSeedreamPro ? 10 : 14;
655
+ if (args.reference_images && args.reference_images.length > maxRefs) {
656
+ return errorResult(
657
+ `${args.model} aceita no m\xE1ximo ${maxRefs} imagens de refer\xEAncia (voc\xEA passou ${args.reference_images.length}).`
658
+ );
659
+ }
660
+ } else if (args.batch_images) {
661
+ return errorResult("batch_images s\xF3 existe no bytedance/seedream-5.0-lite.");
662
+ }
602
663
  const reference_images = await resolveRefs(cfg, args.reference_images);
603
664
  let style_reference;
604
665
  if (isMidjourney && args.style_reference) {
@@ -612,7 +673,12 @@ function registerGenerationTools(server, client, cfg) {
612
673
  reference_images,
613
674
  google_search: args.google_search,
614
675
  image_search: args.image_search,
615
- size: args.size,
676
+ size: args.size ?? (isSeedream ? "2K" : void 0),
677
+ // Série do Seedream Lite: o backend lê o par sequential_image_generation
678
+ // + max_images (nomes da API da BytePlus). `batch_images` é só o nome
679
+ // amigável exposto aqui.
680
+ sequential_image_generation: isSeedreamLite && args.batch_images ? "auto" : void 0,
681
+ max_images: isSeedreamLite && args.batch_images ? args.batch_images : void 0,
616
682
  quality: isMidjourney ? args.mj_quality : args.quality,
617
683
  background: args.background,
618
684
  moderation: args.moderation,
@@ -664,6 +730,10 @@ ${footer}` }, ...blocks]
664
730
  } catch (e) {
665
731
  const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
666
732
  if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
733
+ if (isBudgetBlockedError(msg)) {
734
+ return errorResult(`${apiErrorToUserMessage(e)}
735
+ ${BUDGET_BLOCKED_GUIDANCE}`);
736
+ }
667
737
  return errorResult(apiErrorToUserMessage(e));
668
738
  }
669
739
  }
@@ -721,6 +791,10 @@ ${footer}` }, ...blocks] };
721
791
  } catch (e) {
722
792
  const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
723
793
  if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
794
+ if (isBudgetBlockedError(msg)) {
795
+ return errorResult(`${apiErrorToUserMessage(e)}
796
+ ${BUDGET_BLOCKED_GUIDANCE}`);
797
+ }
724
798
  return errorResult(apiErrorToUserMessage(e));
725
799
  }
726
800
  }
@@ -731,7 +805,9 @@ ${footer}` }, ...blocks] };
731
805
  title: "Gerar v\xEDdeo",
732
806
  description: "Gera um v\xEDdeo curto usando o IA Lab da BePlus. LENTO (geralmente 1-5+ min). Aguarda at\xE9 ~2 min; se n\xE3o terminar, retorna o generation_id e o status \u2014 ent\xE3o use check_generation. Consome diamantes (mais caro para dura\xE7\xF5es/resolu\xE7\xF5es maiores). Suporta image-to-video (`first_frame_image`), \xFAltimo frame (`last_frame_image`), refer\xEAncias de imagem/v\xEDdeo/\xE1udio, prompt negativo, \xE1udio gerado e mais \u2014 os campos aceitos variam por modelo (consulte list_models). `kling-v2.6-motion-control` usa `first_frame_image` + `motion_video`. Imagens com rosto de pessoa REAL s\xE3o bloqueadas pela modera\xE7\xE3o do provedor (use ilustra\xE7\xF5es/personagens)." + TEAM_GATING_NOTE,
733
807
  inputSchema: {
734
- prompt: z.string().min(1).max(4e3).describe("Descri\xE7\xE3o do v\xEDdeo desejado."),
808
+ // Sem teto local — ver nota em generate_image. O Seedance não publica
809
+ // limite; o Kling publica 2500 e o backend recusa dizendo quanto cortar.
810
+ prompt: z.string().min(1).describe("Descri\xE7\xE3o do v\xEDdeo desejado."),
735
811
  model: z.enum(VIDEO_MODELS).default(DEFAULTS.videoModel).describe("seedance-2.0-fast = barato/r\xE1pido; seedance-2.0 = melhor; kling-v3-video = alternativa."),
736
812
  aspect_ratio: z.enum(ASPECT_RATIOS).optional().describe("Propor\xE7\xE3o. Padr\xE3o 16:9."),
737
813
  resolution: z.enum(VIDEO_RESOLUTIONS).optional().describe("480p/720p/1080p/4K (1080p/4K S\xD3 no seedance-2.0 standard; fast e mini aceitam apenas 480p/720p). Padr\xE3o 720p."),
@@ -813,6 +889,10 @@ ${footer}`);
813
889
  } catch (e) {
814
890
  const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
815
891
  if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
892
+ if (isBudgetBlockedError(msg)) {
893
+ return errorResult(`${apiErrorToUserMessage(e)}
894
+ ${BUDGET_BLOCKED_GUIDANCE}`);
895
+ }
816
896
  return errorResult(apiErrorToUserMessage(e));
817
897
  }
818
898
  }
@@ -920,14 +1000,29 @@ ${list}${flagsLine(status)}` }, ...blocks] };
920
1000
  const model = g?.model ?? p?.model ?? "(modelo desconhecido)";
921
1001
  const prompt = g?.prompt ?? p?.prompt ?? "(sem prompt)";
922
1002
  const configs = g?.input_params ?? p?.configs ?? {};
923
- const inputs = p?.input_image_urls ?? [];
1003
+ const meta = g?.metadata ?? {};
1004
+ const asUrls = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string" && !!x) : [];
1005
+ const asUrl = (v) => typeof v === "string" && v ? v : void 0;
1006
+ const pick = (fromDb, fromTag) => {
1007
+ const db = asUrls(fromDb);
1008
+ return db.length > 0 ? db : asUrls(fromTag);
1009
+ };
1010
+ const images = pick(meta.input_image_urls, p?.input_image_urls);
1011
+ const refVideos = pick(meta.reference_video_urls, p?.reference_video_urls);
1012
+ const refAudios = pick(meta.reference_audio_urls, p?.reference_audio_urls);
1013
+ const motionVideo = asUrl(meta.motion_video_url) ?? asUrl(p?.motion_video_url);
1014
+ const endImage = asUrl(meta.end_image_url) ?? asUrl(p?.end_image_url);
924
1015
  const lines = [
925
1016
  `\u{1F9EC} Dados da gera\xE7\xE3o (${r.source === "db" ? "registro da conta" : "metadata do arquivo"}):`,
926
1017
  `\u2022 Modelo: ${model}`,
927
1018
  `\u2022 Prompt: ${prompt}`,
928
1019
  `\u2022 Configs: ${JSON.stringify(configs)}`
929
1020
  ];
930
- if (inputs.length) lines.push(`\u2022 Inputs: ${inputs.join(", ")}`);
1021
+ if (images.length) lines.push(`\u2022 Imagens de entrada: ${images.join(", ")}`);
1022
+ if (endImage) lines.push(`\u2022 Frame final: ${endImage}`);
1023
+ if (refVideos.length) lines.push(`\u2022 V\xEDdeos de refer\xEAncia: ${refVideos.join(", ")}`);
1024
+ if (refAudios.length) lines.push(`\u2022 \xC1udios de refer\xEAncia: ${refAudios.join(", ")}`);
1025
+ if (motionVideo) lines.push(`\u2022 V\xEDdeo de motion control: ${motionVideo}`);
931
1026
  if (p?.generation_id) lines.push(`\u2022 generation_id: ${p.generation_id}`);
932
1027
  return textResult(lines.join("\n"));
933
1028
  } catch (e) {
@@ -990,15 +1085,21 @@ function registerAudioTools(server, client, cfg) {
990
1085
  const projLine = res.project_code ? `
991
1086
  \u{1F4C1} Projeto: ${res.project_code}${res.project_name ? ` \u2014 ${res.project_name}` : ""}` : "";
992
1087
  const wa = weeklyAllowanceLine(res.weekly_allowance);
1088
+ const budget = budgetLine(res.budget);
993
1089
  return textResult(
994
1090
  `\u2705 \xC1udio gerado (${res.model}, voz ${res.voice}, ${res.duration_sec.toFixed(1)}s).
995
1091
  ${res.url}
996
- ${parts.join(" \xB7 ")}${projLine}` + (wa ? `
1092
+ ${parts.join(" \xB7 ")}${projLine}` + (budget ? `
1093
+ ${budget}` : "") + (wa ? `
997
1094
  ${wa}` : "")
998
1095
  );
999
1096
  } catch (e) {
1000
1097
  const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
1001
1098
  if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
1099
+ if (isBudgetBlockedError(msg)) {
1100
+ return errorResult(`${apiErrorToUserMessage(e)}
1101
+ ${BUDGET_BLOCKED_GUIDANCE}`);
1102
+ }
1002
1103
  return errorResult(apiErrorToUserMessage(e));
1003
1104
  }
1004
1105
  }
@@ -1082,6 +1183,10 @@ ${footer}`);
1082
1183
  } catch (e) {
1083
1184
  const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : "";
1084
1185
  if (isTeamGatingError(msg)) return errorResult(await buildPickProjectMessage(client));
1186
+ if (isBudgetBlockedError(msg)) {
1187
+ return errorResult(`${apiErrorToUserMessage(e)}
1188
+ ${BUDGET_BLOCKED_GUIDANCE}`);
1189
+ }
1085
1190
  return errorResult(apiErrorToUserMessage(e));
1086
1191
  }
1087
1192
  }
@@ -1102,10 +1207,12 @@ function registerAccountTools(server, client, _cfg) {
1102
1207
  try {
1103
1208
  const me = await client.me();
1104
1209
  const wa = weeklyAllowanceLine(me.weekly_allowance);
1210
+ const budget = budgetLine(me.budget);
1105
1211
  return textResult(
1106
1212
  `\u{1F464} ${me.name ?? "(sem nome)"} <${me.email ?? "sem email"}>
1107
1213
  Saldo: ${me.diamonds} \u{1F48E} \xB7 auth: ${me.authType}` + (wa ? `
1108
- ${wa}` : "")
1214
+ ${wa}` : "") + (budget ? `
1215
+ ${budget}` : "")
1109
1216
  );
1110
1217
  } catch (e) {
1111
1218
  return errorResult(apiErrorToUserMessage(e));
@@ -1123,8 +1230,12 @@ ${wa}` : "")
1123
1230
  try {
1124
1231
  const b = await client.balance();
1125
1232
  const wa = weeklyAllowanceLine(b.weekly_allowance);
1126
- return textResult(`Saldo: ${b.diamonds} \u{1F48E}` + (wa ? `
1127
- ${wa}` : ""));
1233
+ const budget = budgetLine(b.budget);
1234
+ return textResult(
1235
+ `Saldo: ${b.diamonds} \u{1F48E}` + (wa ? `
1236
+ ${wa}` : "") + (budget ? `
1237
+ ${budget}` : "")
1238
+ );
1128
1239
  } catch (e) {
1129
1240
  return errorResult(apiErrorToUserMessage(e));
1130
1241
  }
@@ -1242,7 +1353,8 @@ function projectLine(p, active) {
1242
1353
  const marker = active ? "\u25CF " : "\u25CB ";
1243
1354
  const counts = `${p.generation_count ?? 0} gera\xE7\xF5es`;
1244
1355
  const owner = p.client_name ? ` \xB7 cliente: ${p.client_name}` : "";
1245
- return `${marker}${p.code} \u2014 ${p.name} (${counts}${owner})`;
1356
+ const budget = p.budget?.enabled ? ` \xB7 budget ${p.budget.remainingDiamonds}/${p.budget.quotaDiamonds}\u{1F48E}` : "";
1357
+ return `${marker}${p.code} \u2014 ${p.name} (${counts}${owner})${budget}`;
1246
1358
  }
1247
1359
  function registerProjectTools(server, client, _cfg) {
1248
1360
  server.registerTool(
@@ -1262,7 +1374,10 @@ function registerProjectTools(server, client, _cfg) {
1262
1374
  }
1263
1375
  const lines = projects.map((p) => projectLine(p, !!p.is_active_for_user));
1264
1376
  const active = projects.find((p) => p.is_active_for_user);
1265
- const header = active ? `Projeto ativo: ${active.code} \u2014 ${active.name}` : "Nenhum projeto ativo (gera\xE7\xF5es ficam sem projeto).";
1377
+ let header = active ? `Projeto ativo: ${active.code} \u2014 ${active.name}` : "Nenhum projeto ativo (gera\xE7\xF5es ficam sem projeto).";
1378
+ const activeBudget = budgetLine(active?.budget);
1379
+ if (activeBudget) header += `
1380
+ ${activeBudget}`;
1266
1381
  return textResult(`${header}
1267
1382
 
1268
1383
  ${lines.join("\n")}`);
@@ -1313,7 +1428,9 @@ ${lines.join("\n")}`);
1313
1428
  try {
1314
1429
  const { project: active } = await client.setActiveProject(project ?? null);
1315
1430
  if (!active) return textResult("\u2705 Projeto ativo removido \u2014 novas gera\xE7\xF5es ficar\xE3o sem projeto.");
1316
- return textResult(`\u2705 Projeto ativo: ${active.code} \u2014 ${active.name}.`);
1431
+ const budget = budgetLine(active.budget);
1432
+ return textResult(`\u2705 Projeto ativo: ${active.code} \u2014 ${active.name}.` + (budget ? `
1433
+ ${budget}` : ""));
1317
1434
  } catch (e) {
1318
1435
  return errorResult(apiErrorToUserMessage(e));
1319
1436
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beplus-mcp",
3
- "version": "0.13.0",
3
+ "version": "0.16.0",
4
4
  "description": "Conector MCP da equipe BePlus — gere imagens/vídeos/áudio e consulte calls, projetos e clientes pela sua conta BePlus (diamantes, limites e histórico aplicados pela plataforma).",
5
5
  "type": "module",
6
6
  "bin": {