beplus-mcp 0.40.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -1
- package/dist/index.js +122 -34
- package/dist/index.js.map +1 -1
- package/dist/remote-server.d.ts +24 -0
- package/dist/remote-server.js +28814 -0
- package/dist/remote-server.js.map +1 -0
- package/package.json +8 -1
package/README.md
CHANGED
|
@@ -12,6 +12,25 @@ Claude Code/Desktop/Cursor ──stdio──▶ beplus-mcp ──HTTPS (Beare
|
|
|
12
12
|
|
|
13
13
|
Você se autentica com um **Personal Access Token (PAT)** gerado no painel da BePlus. O MCP só age em nome da SUA conta.
|
|
14
14
|
|
|
15
|
+
## Jeito mais fácil: conector por URL (sem Node, sem token)
|
|
16
|
+
|
|
17
|
+
A API da BePlus serve as mesmas ferramentas por HTTP, com login pelo navegador (OAuth).
|
|
18
|
+
|
|
19
|
+
**Claude Code:**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
claude mcp add --transport http --scope user beplus https://api.beplus.academy/api/v1/mcp
|
|
23
|
+
claude mcp login beplus
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
O navegador abre, você entra na BePlus e clica em **Autorizar acesso**. Reinicie o Claude Code.
|
|
27
|
+
|
|
28
|
+
**Claude Desktop e claude.ai:** Configurações > Conectores > Adicionar conector personalizado, cole
|
|
29
|
+
`https://api.beplus.academy/api/v1/mcp`, clique em Conectar e em Autorizar acesso.
|
|
30
|
+
|
|
31
|
+
No conector por URL as ferramentas rodam no servidor: arquivo de mídia vai por **URL pública**, não por caminho do
|
|
32
|
+
seu computador. Para subir arquivo local, use o pacote abaixo.
|
|
33
|
+
|
|
15
34
|
## 1. Gere um Personal Access Token
|
|
16
35
|
|
|
17
36
|
No app da BePlus → **Configurações da conta → Tokens de acesso** → *Criar token*. Copie o token (`pat_…`) — ele só aparece uma vez.
|
|
@@ -84,7 +103,7 @@ Reinicie o cliente. Rode a tool **`whoami`** para confirmar o vínculo.
|
|
|
84
103
|
| `generate_image` | Gera imagem (nano-banana / gpt-image-2). Bloqueante ~90s; retorna URL + imagem inline. Suporta refs, tamanho/qualidade, e extras do gpt-image-2 (background, moderation, output_format/compression). |
|
|
85
104
|
| `generate_video` | Gera vídeo (Seedance / Kling). Aguarda ~120s; senão devolve o id pra `check_generation`. Suporta first/last frame, refs de imagem/vídeo/áudio, prompt negativo, áudio gerado, mode e motion-control. |
|
|
86
105
|
| `upscale` | Melhora a resolução de uma imagem ou vídeo que já existe (Topaz). Detecta o tipo pelo arquivo. Imagem até 4x (~20s, a partir de 6💎); vídeo até 30s e saída até 1080p, custa por bloco de 10s e **descarta o áudio**. Mede o arquivo sozinho para o preço sair certo. ⚠️ Vídeo é restrito à equipe. |
|
|
87
|
-
| `generate_audio` | Sintetiza fala
|
|
106
|
+
| `generate_audio` | Sintetiza fala com Gemini TTS ou SeedAudio 1.0 com referência de voz. Padrão `gemini-3.8-flash-lite-tts`; os modelos 3.8 aceitam vozes da biblioteca, tags vocais (`<laugh>`, `<sigh>`...) e `style` por locutor; os legados só as 30 vozes clássicas. Síncrono. |
|
|
88
107
|
| `list_tts_voices` | Lista as vozes da biblioteca do TTS 3.8 (filtros: idioma, gênero, tom, contexto, busca) com o id para usar em `voice`. Somente leitura. |
|
|
89
108
|
| `list_custom_voices` | Lista suas vozes próprias do TTS 3.8 (máx. 3) com o `voice_xxx` para usar em `voice`. Clonar a própria voz só no app web. Somente leitura. |
|
|
90
109
|
| `design_voice` | Cria uma voz própria a partir de uma descrição. Gasta 💎 e leva ~30 s. |
|
|
@@ -213,3 +232,13 @@ BEPLUS_API_TOKEN=pat_… npx @modelcontextprotocol/inspector node dist/index.js
|
|
|
213
232
|
- **stdout** é o canal JSON-RPC — todo log vai pra **stderr** e o token nunca é logado.
|
|
214
233
|
- Limites, créditos, visibilidade e logging são enforçados pelo backend; um PAT vazado é limitado aos caps/diamantes do dono e pode ser revogado no painel.
|
|
215
234
|
- `list_models` é a fonte de verdade em runtime; os enums do pacote podem ficar atrás quando o backend adiciona modelos.
|
|
235
|
+
|
|
236
|
+
### SeedAudio 1.0
|
|
237
|
+
|
|
238
|
+
Use `generate_audio` com `model: "seed-audio-1.0"`, `text` e direção opcional em `style_instruction`. Omita `voice` ou use `seed-auto`. Texto e direção somados: até 2700 caracteres. Sem diálogo nem vozes Gemini.
|
|
239
|
+
|
|
240
|
+
Para referência, passe `seed_reference` como arquivo local no stdio ou URL pública no conector remoto. Um áudio de 1 a 30 segundos, até 3 MiB, com `seed_consent: true` após autorização explícita da pessoa. O MCP envia os bytes diretamente ao backend, sem publicar um novo arquivo de referência. O modo remoto mantém a proteção de caminhos locais e rede privada.
|
|
241
|
+
|
|
242
|
+
A resposta inclui o ID da geração. Para repetir a voz, use esse ID em `seed_reference_generation_id` e confirme novamente `seed_consent`. Só referências de gerações da própria conta são aceitas. Essa referência por pedido é separada das vozes próprias do Gemini.
|
|
243
|
+
|
|
244
|
+
O conector remoto também precisa da nova versão do pacote no backend. Ordem de entrega: configurar `SEEDAUDIO_API_KEY` e disponibilizar o backend TTS, atualizar o pacote MCP no backend, depois disponibilizar IA Lab e Canvas.
|
package/dist/index.js
CHANGED
|
@@ -3303,8 +3303,8 @@ var require_utils = __commonJS({
|
|
|
3303
3303
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3304
3304
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3305
3305
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3306
|
-
function reescapeHostDelimiters(host,
|
|
3307
|
-
const re =
|
|
3306
|
+
function reescapeHostDelimiters(host, isIP2) {
|
|
3307
|
+
const re = isIP2 ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3308
3308
|
re.lastIndex = 0;
|
|
3309
3309
|
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3310
3310
|
}
|
|
@@ -3786,7 +3786,7 @@ var require_fast_uri = __commonJS({
|
|
|
3786
3786
|
fragment: void 0
|
|
3787
3787
|
};
|
|
3788
3788
|
let malformedAuthorityOrPort = false;
|
|
3789
|
-
let
|
|
3789
|
+
let isIP2 = false;
|
|
3790
3790
|
if (options.reference === "suffix") {
|
|
3791
3791
|
if (options.scheme) {
|
|
3792
3792
|
uri = options.scheme + ":" + uri;
|
|
@@ -3816,9 +3816,9 @@ var require_fast_uri = __commonJS({
|
|
|
3816
3816
|
if (ipv4result === false) {
|
|
3817
3817
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
3818
3818
|
parsed.host = ipv6result.host.toLowerCase();
|
|
3819
|
-
|
|
3819
|
+
isIP2 = ipv6result.isIPV6;
|
|
3820
3820
|
} else {
|
|
3821
|
-
|
|
3821
|
+
isIP2 = true;
|
|
3822
3822
|
}
|
|
3823
3823
|
}
|
|
3824
3824
|
if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) {
|
|
@@ -3835,7 +3835,7 @@ var require_fast_uri = __commonJS({
|
|
|
3835
3835
|
}
|
|
3836
3836
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
3837
3837
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3838
|
-
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) &&
|
|
3838
|
+
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP2 === false && nonSimpleDomain(parsed.host)) {
|
|
3839
3839
|
try {
|
|
3840
3840
|
parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
|
|
3841
3841
|
} catch (e) {
|
|
@@ -3849,7 +3849,7 @@ var require_fast_uri = __commonJS({
|
|
|
3849
3849
|
parsed.scheme = unescape(parsed.scheme);
|
|
3850
3850
|
}
|
|
3851
3851
|
if (parsed.host !== void 0) {
|
|
3852
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host),
|
|
3852
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP2);
|
|
3853
3853
|
}
|
|
3854
3854
|
}
|
|
3855
3855
|
if (parsed.path) {
|
|
@@ -21619,7 +21619,7 @@ var BeplusClient = class {
|
|
|
21619
21619
|
});
|
|
21620
21620
|
}
|
|
21621
21621
|
tts(body2) {
|
|
21622
|
-
return this.request("POST", "/tts", { body: body2, timeoutMs: 9e4 });
|
|
21622
|
+
return this.request("POST", "/tts", { body: body2, timeoutMs: body2.model === "seed-audio-1.0" ? 18e4 : 9e4 });
|
|
21623
21623
|
}
|
|
21624
21624
|
ttsVoices(query) {
|
|
21625
21625
|
return this.request("GET", "/tts/voices", { query, timeoutMs: 2e4 });
|
|
@@ -21749,6 +21749,69 @@ async function pollUntilDone(client, generationId, budgetMs, jitterSeed = 0) {
|
|
|
21749
21749
|
|
|
21750
21750
|
// src/media.ts
|
|
21751
21751
|
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
21752
|
+
|
|
21753
|
+
// src/remote.ts
|
|
21754
|
+
import { lookup } from "dns/promises";
|
|
21755
|
+
import { isIP } from "net";
|
|
21756
|
+
var remote = false;
|
|
21757
|
+
function refuseLocalPath(ref) {
|
|
21758
|
+
if (!remote) return;
|
|
21759
|
+
throw new ApiError(
|
|
21760
|
+
0,
|
|
21761
|
+
`"${ref}" \xE9 um caminho de arquivo, e esta conex\xE3o roda no servidor da BePlus, que n\xE3o enxerga o seu computador. Mande uma URL p\xFAblica (http/https) ou suba o arquivo pela plataforma e use o link dele.`
|
|
21762
|
+
);
|
|
21763
|
+
}
|
|
21764
|
+
var MAX_REDIRECTS = 5;
|
|
21765
|
+
function v4Private(ip) {
|
|
21766
|
+
const [a, b] = ip.split(".").map(Number);
|
|
21767
|
+
return a === 0 || a === 10 || a === 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 100 && b >= 64 && b <= 127 || a >= 224;
|
|
21768
|
+
}
|
|
21769
|
+
function privateAddress(ip) {
|
|
21770
|
+
if (isIP(ip) === 4) return v4Private(ip);
|
|
21771
|
+
const v6 = ip.toLowerCase();
|
|
21772
|
+
const dotted = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(v6);
|
|
21773
|
+
if (dotted) return v4Private(dotted[1]);
|
|
21774
|
+
const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(v6);
|
|
21775
|
+
if (hex) {
|
|
21776
|
+
const hi = parseInt(hex[1], 16);
|
|
21777
|
+
const lo = parseInt(hex[2], 16);
|
|
21778
|
+
return v4Private(`${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`);
|
|
21779
|
+
}
|
|
21780
|
+
return v6 === "::" || v6 === "::1" || /^f[cd]/.test(v6) || /^fe[89ab]/.test(v6) || /^ff/.test(v6);
|
|
21781
|
+
}
|
|
21782
|
+
async function assertPublic(url) {
|
|
21783
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
21784
|
+
throw new ApiError(0, `S\xF3 baixo endere\xE7o http ou https: ${url.href}`);
|
|
21785
|
+
}
|
|
21786
|
+
const host = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
21787
|
+
if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".internal") || host.endsWith(".local")) {
|
|
21788
|
+
throw new ApiError(0, `Endere\xE7o interno n\xE3o \xE9 aceito: ${url.host}`);
|
|
21789
|
+
}
|
|
21790
|
+
const addrs = isIP(host) ? [{ address: host }] : await lookup(host, { all: true }).catch(() => []);
|
|
21791
|
+
if (addrs.length === 0) throw new ApiError(0, `N\xE3o encontrei o endere\xE7o ${url.host}.`);
|
|
21792
|
+
if (addrs.some((a) => privateAddress(a.address))) {
|
|
21793
|
+
throw new ApiError(0, `Endere\xE7o de rede privada n\xE3o \xE9 aceito: ${url.host}`);
|
|
21794
|
+
}
|
|
21795
|
+
}
|
|
21796
|
+
async function safeFetch(input, init = {}) {
|
|
21797
|
+
if (!remote) return fetch(input, init);
|
|
21798
|
+
let url = new URL(input);
|
|
21799
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
21800
|
+
await assertPublic(url);
|
|
21801
|
+
const res = await fetch(url, { ...init, redirect: "manual" });
|
|
21802
|
+
const location = res.headers.get("location");
|
|
21803
|
+
if (res.status >= 300 && res.status < 400 && location) {
|
|
21804
|
+
await res.body?.cancel().catch(() => {
|
|
21805
|
+
});
|
|
21806
|
+
url = new URL(location, url);
|
|
21807
|
+
continue;
|
|
21808
|
+
}
|
|
21809
|
+
return res;
|
|
21810
|
+
}
|
|
21811
|
+
throw new ApiError(0, `Redirecionamentos demais ao baixar ${input}.`);
|
|
21812
|
+
}
|
|
21813
|
+
|
|
21814
|
+
// src/media.ts
|
|
21752
21815
|
import { basename, extname, isAbsolute, resolve } from "path";
|
|
21753
21816
|
import { homedir as homedir2 } from "os";
|
|
21754
21817
|
import { fileURLToPath } from "url";
|
|
@@ -21836,6 +21899,7 @@ function isRemoteRef(ref) {
|
|
|
21836
21899
|
return /^https?:\/\//i.test(ref);
|
|
21837
21900
|
}
|
|
21838
21901
|
function toLocalPath(ref) {
|
|
21902
|
+
refuseLocalPath(ref);
|
|
21839
21903
|
if (ref.startsWith("file://")) return fileURLToPath(ref);
|
|
21840
21904
|
if (ref === "~") return homedir2();
|
|
21841
21905
|
if (ref.startsWith("~/")) return resolve(homedir2(), ref.slice(2));
|
|
@@ -21855,7 +21919,7 @@ function tooLarge(name, bytes) {
|
|
|
21855
21919
|
`"${name}" ${size} acima do teto de 95 MB para subir pela API (o proxy da Cloudflare corta corpo acima de 100 MB antes de chegar no servidor). Comprima ou corte o arquivo (ex.: ffmpeg -i entrada.mp4 -crf 26 saida.mp4) e tente de novo.`
|
|
21856
21920
|
);
|
|
21857
21921
|
}
|
|
21858
|
-
async function loadLocal(ref) {
|
|
21922
|
+
async function loadLocal(ref, maxBytes = MAX_UPLOAD_BYTES) {
|
|
21859
21923
|
const abs = toLocalPath(ref);
|
|
21860
21924
|
const name = basename(abs);
|
|
21861
21925
|
let size;
|
|
@@ -21867,16 +21931,17 @@ async function loadLocal(ref) {
|
|
|
21867
21931
|
`Arquivo local n\xE3o encontrado: "${ref}". Passe um caminho v\xE1lido ou uma URL p\xFAblica (http/https).`
|
|
21868
21932
|
);
|
|
21869
21933
|
}
|
|
21870
|
-
if (size >
|
|
21934
|
+
if (size > maxBytes) throw maxBytes === MAX_UPLOAD_BYTES ? tooLarge(name, size) : new ApiError(413, `O arquivo deve ter at\xE9 ${mb(maxBytes)} MB.`);
|
|
21871
21935
|
return { buf: await readFile2(abs), name, mime: mimeForName(abs), localPath: abs };
|
|
21872
21936
|
}
|
|
21873
|
-
async function loadRemote(url) {
|
|
21937
|
+
async function loadRemote(url, maxBytes = MAX_UPLOAD_BYTES) {
|
|
21874
21938
|
const u = new URL(url);
|
|
21875
21939
|
let name = decodeURIComponent(basename(u.pathname)) || "arquivo";
|
|
21876
21940
|
let res;
|
|
21877
21941
|
try {
|
|
21878
|
-
res = await
|
|
21879
|
-
} catch {
|
|
21942
|
+
res = await safeFetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
21943
|
+
} catch (e) {
|
|
21944
|
+
if (e instanceof ApiError) throw e;
|
|
21880
21945
|
throw new ApiError(0, `N\xE3o consegui baixar ${url} (rede ou tempo esgotado).`);
|
|
21881
21946
|
}
|
|
21882
21947
|
if (!res.ok) {
|
|
@@ -21885,10 +21950,10 @@ async function loadRemote(url) {
|
|
|
21885
21950
|
throw new ApiError(res.status, `N\xE3o consegui baixar ${url}: a origem respondeu HTTP ${res.status}.`);
|
|
21886
21951
|
}
|
|
21887
21952
|
const declared = Number(res.headers.get("content-length") ?? NaN);
|
|
21888
|
-
if (Number.isFinite(declared) && declared >
|
|
21953
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
21889
21954
|
await res.body?.cancel().catch(() => {
|
|
21890
21955
|
});
|
|
21891
|
-
throw tooLarge(name, declared);
|
|
21956
|
+
throw maxBytes === MAX_UPLOAD_BYTES ? tooLarge(name, declared) : new ApiError(413, `O arquivo deve ter at\xE9 ${mb(maxBytes)} MB.`);
|
|
21892
21957
|
}
|
|
21893
21958
|
const chunks = [];
|
|
21894
21959
|
let total = 0;
|
|
@@ -21899,10 +21964,10 @@ async function loadRemote(url) {
|
|
|
21899
21964
|
const { done, value } = await reader.read();
|
|
21900
21965
|
if (done) break;
|
|
21901
21966
|
total += value.byteLength;
|
|
21902
|
-
if (total >
|
|
21967
|
+
if (total > maxBytes) {
|
|
21903
21968
|
await reader.cancel().catch(() => {
|
|
21904
21969
|
});
|
|
21905
|
-
throw tooLarge(name, null);
|
|
21970
|
+
throw maxBytes === MAX_UPLOAD_BYTES ? tooLarge(name, null) : new ApiError(413, `O arquivo deve ter at\xE9 ${mb(maxBytes)} MB.`);
|
|
21906
21971
|
}
|
|
21907
21972
|
chunks.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
|
|
21908
21973
|
}
|
|
@@ -22217,6 +22282,7 @@ var MUSIC_TASK_TYPES = ["create", "extend", "cover", "upload_extend", "upload_co
|
|
|
22217
22282
|
var MUSIC_TOOLS = ["wav", "midi", "stems-basic", "stems-full", "bpm", "lyrics-timeline"];
|
|
22218
22283
|
var VOCAL_GENDERS = ["f", "m"];
|
|
22219
22284
|
var TTS_MODELS = [
|
|
22285
|
+
"seed-audio-1.0",
|
|
22220
22286
|
"gemini-3.8-flash-lite-tts",
|
|
22221
22287
|
"gemini-3.8-flash-tts",
|
|
22222
22288
|
"gemini-3.1-flash-tts-preview",
|
|
@@ -23218,6 +23284,7 @@ function isRemote(ref) {
|
|
|
23218
23284
|
return /^https?:\/\//i.test(ref);
|
|
23219
23285
|
}
|
|
23220
23286
|
function toLocalPath2(ref) {
|
|
23287
|
+
refuseLocalPath(ref);
|
|
23221
23288
|
if (ref.startsWith("file://")) return fileURLToPath2(ref);
|
|
23222
23289
|
if (ref === "~") return homedir3();
|
|
23223
23290
|
if (ref.startsWith("~/")) return resolve2(homedir3(), ref.slice(2));
|
|
@@ -23243,7 +23310,7 @@ async function readAt(ref, offset, len) {
|
|
|
23243
23310
|
const ctrl = new AbortController();
|
|
23244
23311
|
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
23245
23312
|
try {
|
|
23246
|
-
const res = await
|
|
23313
|
+
const res = await safeFetch(ref, {
|
|
23247
23314
|
headers: { Range: `bytes=${offset}-${offset + len - 1}` },
|
|
23248
23315
|
signal: ctrl.signal
|
|
23249
23316
|
});
|
|
@@ -23286,7 +23353,7 @@ async function readHead(ref, bytes) {
|
|
|
23286
23353
|
const ctrl = new AbortController();
|
|
23287
23354
|
const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
23288
23355
|
try {
|
|
23289
|
-
const res = await
|
|
23356
|
+
const res = await safeFetch(ref, {
|
|
23290
23357
|
headers: { Range: `bytes=0-${bytes - 1}` },
|
|
23291
23358
|
signal: ctrl.signal
|
|
23292
23359
|
});
|
|
@@ -23430,7 +23497,7 @@ function probeMp4(b) {
|
|
|
23430
23497
|
}
|
|
23431
23498
|
async function fileSizeOf(ref) {
|
|
23432
23499
|
if (!isRemote(ref)) return (await stat3(toLocalPath2(ref))).size;
|
|
23433
|
-
const res = await
|
|
23500
|
+
const res = await safeFetch(ref, { method: "HEAD" });
|
|
23434
23501
|
return Number(res.headers.get("content-length") ?? 0);
|
|
23435
23502
|
}
|
|
23436
23503
|
var ProbeError = class extends Error {
|
|
@@ -23790,7 +23857,7 @@ var VOICE_HELP_TOP = `${VOICE_HELP} Ou o id de uma voz pr\xF3pria (voice_xxx, de
|
|
|
23790
23857
|
var voiceSchema = external_exports.string().refine((v) => VOICES.includes(v) || LIBRARY_VOICE_ID.test(v), {
|
|
23791
23858
|
message: "Voz inv\xE1lida: use uma das 30 vozes cl\xE1ssicas (ex.: Kore) ou um id da biblioteca (ex.: pt-br-advisor-10)."
|
|
23792
23859
|
});
|
|
23793
|
-
var topVoiceSchema = external_exports.string().refine((v) => VOICES.includes(v) || LIBRARY_VOICE_ID.test(v) || CUSTOM_VOICE_ID.test(v), {
|
|
23860
|
+
var topVoiceSchema = external_exports.string().refine((v) => v === "seed-auto" || VOICES.includes(v) || LIBRARY_VOICE_ID.test(v) || CUSTOM_VOICE_ID.test(v), {
|
|
23794
23861
|
message: "Voz inv\xE1lida: use uma das 30 vozes cl\xE1ssicas (ex.: Kore), um id da biblioteca (ex.: pt-br-advisor-10) ou uma voz pr\xF3pria (voice_xxx)."
|
|
23795
23862
|
});
|
|
23796
23863
|
function isCustomVoice(v) {
|
|
@@ -23809,10 +23876,13 @@ function registerAudioTools(server, client, cfg) {
|
|
|
23809
23876
|
"generate_audio",
|
|
23810
23877
|
{
|
|
23811
23878
|
title: "Gerar \xE1udio (TTS)",
|
|
23812
|
-
description: 'Sintetiza fala a partir de texto
|
|
23879
|
+
description: 'Sintetiza fala a partir de texto e retorna a URL usando o IA Lab da BePlus. SeedAudio 1.0 (seed-audio-1.0) aceita dire\xE7\xE3o e uma refer\xEAncia de voz opcional via seed_reference ou seed_reference_generation_id, com seed_consent. Texto e dire\xE7\xE3o somados at\xE9 2700 caracteres. N\xE3o aceita di\xE1logo nem vozes Gemini. Modelos Gemini 3.8 (`gemini-3.8-flash-lite-tts`, padr\xE3o, r\xE1pido e barato; `gemini-3.8-flash-tts`, qualidade de est\xFAdio, melhor para di\xE1logo e narra\xE7\xE3o longa) aceitam: vozes da biblioteca (milhares, por idioma; liste com `list_tts_voices` e passe o id em `voice`), tags vocais no texto como <laugh> <chuckle> <sigh> <breath> <gasp> <cough> <sneeze> <throat-clearing> <yawn> <groan> <scream> <whimper> <argh> <short pause> <long pause> (viram som de verdade), e estilo por locutor (`style` em cada speaker). No 3.8, `style_instruction` \xE9 dire\xE7\xE3o e N\xC3O \xE9 lida em voz alta. Os modelos legados (`gemini-3.1-flash-tts-preview`, `gemini-2.5-pro-preview-tts`) s\xF3 aceitam as 30 vozes cl\xE1ssicas (Kore, Puck, Charon...) e n\xE3o t\xEAm voz da biblioteca. Vozes pr\xF3prias (voice_xxx; liste com `list_custom_voices`, crie com `design_voice`) s\xF3 no 3.8 e sem `multi_speaker`. No 3.8, dire\xE7\xE3o por linha: `(sussurrando) texto` no in\xEDcio da linha vale at\xE9 a pr\xF3xima dire\xE7\xE3o; no di\xE1logo, `Ana (rindo): fala`; rea\xE7\xF5es `|hmm|` `|aham|` no meio da fala (s\xF3 di\xE1logo). Opcional `style_instruction` (ex.: "fale animado e pausado") e di\xE1logo com 2 locutores via `multi_speaker`. Consome diamantes por segundo de \xE1udio.' + TEAM_GATING_NOTE,
|
|
23813
23880
|
inputSchema: {
|
|
23814
23881
|
text: external_exports.string().min(1).max(1e4).describe("Texto a ser falado."),
|
|
23815
|
-
voice: topVoiceSchema.
|
|
23882
|
+
voice: topVoiceSchema.optional().describe(VOICE_HELP_TOP + " No SeedAudio, omita ou use seed-auto."),
|
|
23883
|
+
seed_reference: external_exports.string().min(1).optional().describe("SeedAudio: arquivo local (stdio) ou URL p\xFAblica de uma voz autorizada, de 1 a 30 s e at\xE9 3 MiB. N\xE3o \xE9 publicado pelo MCP."),
|
|
23884
|
+
seed_reference_generation_id: external_exports.string().uuid().optional().describe("Reutiliza a refer\xEAncia de uma gera\xE7\xE3o SeedAudio sua. Alternativa a seed_reference."),
|
|
23885
|
+
seed_consent: external_exports.literal(true).optional().describe("Confirme apenas ap\xF3s autoriza\xE7\xE3o expl\xEDcita da pessoa para usar sua voz. Obrigat\xF3rio com refer\xEAncia."),
|
|
23816
23886
|
model: external_exports.enum(TTS_MODELS).default(DEFAULTS.ttsModel).describe("Modelo TTS."),
|
|
23817
23887
|
style_instruction: external_exports.string().max(2e3).optional().describe("Instru\xE7\xE3o de estilo/tom aplicada \xE0 fala."),
|
|
23818
23888
|
multi_speaker: external_exports.object({
|
|
@@ -23824,9 +23894,17 @@ function registerAudioTools(server, client, cfg) {
|
|
|
23824
23894
|
},
|
|
23825
23895
|
async (args) => {
|
|
23826
23896
|
try {
|
|
23897
|
+
const seed = args.model === "seed-audio-1.0";
|
|
23898
|
+
const voice = args.voice ?? (seed ? "seed-auto" : DEFAULTS.voice);
|
|
23899
|
+
const hasReference = !!(args.seed_reference || args.seed_reference_generation_id);
|
|
23900
|
+
if (!seed && (hasReference || args.seed_consent || voice === "seed-auto")) return errorResult("Os controles SeedAudio exigem seed-audio-1.0.");
|
|
23901
|
+
if (seed && (voice !== "seed-auto" || args.multi_speaker)) return errorResult("SeedAudio usa seed-auto e n\xE3o aceita di\xE1logo.");
|
|
23902
|
+
if (args.seed_reference && args.seed_reference_generation_id) return errorResult("Use apenas uma origem de refer\xEAncia.");
|
|
23903
|
+
if (hasReference && !args.seed_consent) return errorResult("Confirme a autoriza\xE7\xE3o da voz em seed_consent.");
|
|
23904
|
+
if (seed && args.text.trim().length + (args.style_instruction?.trim().length ?? 0) > 2700) return errorResult("SeedAudio: texto e dire\xE7\xE3o devem somar at\xE9 2700 caracteres.");
|
|
23827
23905
|
const ms = args.multi_speaker;
|
|
23828
|
-
const voices = [
|
|
23829
|
-
if (isCustomVoice(
|
|
23906
|
+
const voices = [voice, ms?.speaker1.voice, ms?.speaker2.voice].filter((v) => !!v);
|
|
23907
|
+
if (isCustomVoice(voice)) {
|
|
23830
23908
|
if (!TTS_MODELS_38.includes(args.model)) {
|
|
23831
23909
|
return errorResult("Vozes pr\xF3prias s\xF3 funcionam nos modelos Gemini 3.8 (gemini-3.8-flash-lite-tts, gemini-3.8-flash-tts).");
|
|
23832
23910
|
}
|
|
@@ -23834,7 +23912,7 @@ function registerAudioTools(server, client, cfg) {
|
|
|
23834
23912
|
return errorResult("Vozes pr\xF3prias n\xE3o funcionam no modo di\xE1logo (multi_speaker). Use uma voz da biblioteca ou cl\xE1ssica.");
|
|
23835
23913
|
}
|
|
23836
23914
|
}
|
|
23837
|
-
if (!TTS_MODELS_38.includes(args.model)) {
|
|
23915
|
+
if (!seed && !TTS_MODELS_38.includes(args.model)) {
|
|
23838
23916
|
if (voices.some(isLibraryVoice)) {
|
|
23839
23917
|
return errorResult(
|
|
23840
23918
|
`Vozes da biblioteca s\xF3 funcionam nos modelos Gemini 3.8 (gemini-3.8-flash-lite-tts, gemini-3.8-flash-tts). O modelo ${args.model} s\xF3 aceita as 30 vozes cl\xE1ssicas (Kore, Puck, Charon...).`
|
|
@@ -23844,9 +23922,18 @@ function registerAudioTools(server, client, cfg) {
|
|
|
23844
23922
|
return errorResult("O estilo por locutor (`style`) s\xF3 funciona nos modelos Gemini 3.8.");
|
|
23845
23923
|
}
|
|
23846
23924
|
}
|
|
23925
|
+
let seedReference;
|
|
23926
|
+
if (args.seed_reference) {
|
|
23927
|
+
const file = await (isRemoteRef(args.seed_reference) ? loadRemote(args.seed_reference, 3 * 1024 * 1024) : loadLocal(args.seed_reference, 3 * 1024 * 1024));
|
|
23928
|
+
if (!file.buf.length || file.buf.length > 3 * 1024 * 1024) return errorResult("A refer\xEAncia deve ter at\xE9 3 MiB.");
|
|
23929
|
+
seedReference = { audio: file.buf.toString("base64"), mime: file.mime };
|
|
23930
|
+
}
|
|
23847
23931
|
const body2 = compact({
|
|
23932
|
+
seedReference,
|
|
23933
|
+
seedReferenceGenerationId: args.seed_reference_generation_id,
|
|
23934
|
+
seedConsent: args.seed_consent,
|
|
23848
23935
|
text: args.text,
|
|
23849
|
-
voice
|
|
23936
|
+
voice,
|
|
23850
23937
|
model: args.model,
|
|
23851
23938
|
styleInstruction: args.style_instruction,
|
|
23852
23939
|
multiSpeaker: args.multi_speaker,
|
|
@@ -23856,12 +23943,13 @@ function registerAudioTools(server, client, cfg) {
|
|
|
23856
23943
|
const parts = [`Custo: ${res.diamond_cost} \u{1F48E}`];
|
|
23857
23944
|
if (res.diamonds_refunded) parts.push(`Estorno: ${res.diamonds_refunded} \u{1F48E}`);
|
|
23858
23945
|
const projLine = res.project_code ? `
|
|
23859
|
-
\u{1F4C1} Projeto: ${res.project_code}${res.project_name ?
|
|
23946
|
+
\u{1F4C1} Projeto: ${res.project_code}${res.project_name ? `: ${res.project_name}` : ""}` : "";
|
|
23860
23947
|
const wa = weeklyAllowanceLine(res.weekly_allowance);
|
|
23861
23948
|
const budget = budgetLine(res.budget);
|
|
23862
23949
|
return textResult(
|
|
23863
|
-
`\u2705 \xC1udio gerado (${res.model}, voz ${res.voice}, ${res.duration_sec.toFixed(1)}s).
|
|
23950
|
+
`\u2705 \xC1udio gerado (${res.model}, voz ${seed ? "por refer\xEAncia ou descri\xE7\xE3o" : res.voice}, ${res.duration_sec.toFixed(1)}s).
|
|
23864
23951
|
${res.url}
|
|
23952
|
+
Gera\xE7\xE3o: ${res.generation_id}
|
|
23865
23953
|
${parts.join(" \xB7 ")}${projLine}` + (budget ? `
|
|
23866
23954
|
${budget}` : "") + (wa ? `
|
|
23867
23955
|
${wa}` : "")
|
|
@@ -24707,7 +24795,7 @@ var KINDS = {
|
|
|
24707
24795
|
data: { model: "gemini-3.8-flash-lite-tts", voice: "Algieba", speed: "normal" },
|
|
24708
24796
|
inputs: ["text"],
|
|
24709
24797
|
outputs: ["audio", "approved"],
|
|
24710
|
-
dynamic: "style conforme o modelo"
|
|
24798
|
+
dynamic: "style conforme o modelo; SeedAudio 1.0 tem reference (audio), usa voice: seed-auto e seedConsent: true com refer\xEAncia; alternativa seedReferenceGenerationId de gera\xE7\xE3o pr\xF3pria"
|
|
24711
24799
|
},
|
|
24712
24800
|
music: {
|
|
24713
24801
|
title: "Trilha",
|
|
@@ -26203,10 +26291,10 @@ function withExtension(name, file) {
|
|
|
26203
26291
|
return ext && extname3(clean).toLowerCase() !== ext.toLowerCase() ? `${clean}${ext}` : clean;
|
|
26204
26292
|
}
|
|
26205
26293
|
async function uploadMedia(cfg, source, name) {
|
|
26206
|
-
const
|
|
26207
|
-
const loaded =
|
|
26294
|
+
const remote2 = isRemoteRef(source);
|
|
26295
|
+
const loaded = remote2 ? await loadRemote(source) : await loadLocal(source);
|
|
26208
26296
|
const file = { ...loaded, name: withExtension(name, loaded) };
|
|
26209
|
-
const reused =
|
|
26297
|
+
const reused = remote2 && new URL(source).host === BEPLUS_STORAGE_HOST;
|
|
26210
26298
|
const url = reused ? source : await uploadBytes(cfg, file);
|
|
26211
26299
|
const kind = kindOfMime(file.mime);
|
|
26212
26300
|
let meta = null;
|
|
@@ -27757,7 +27845,7 @@ N\xF3s: ${Object.entries(ids).map(([k, v]) => `${k}=${v}`).join(" \xB7 ")}` : ""
|
|
|
27757
27845
|
import { createRequire } from "module";
|
|
27758
27846
|
var require2 = createRequire(import.meta.url);
|
|
27759
27847
|
function readVersion() {
|
|
27760
|
-
if ("0.
|
|
27848
|
+
if ("0.42.0") return "0.42.0";
|
|
27761
27849
|
try {
|
|
27762
27850
|
const pkg = require2("../package.json");
|
|
27763
27851
|
return pkg.version ?? "0.0.0-unknown";
|