sapiens-mcp 1.38.0 → 1.40.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/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ /*
3
+ * sapiens-mcp · o exoesqueleto do Sapiens Sintéticos.
4
+ * borderless build. sapiensinteticos.com
5
+ * Exoesqueleto, não piloto.
6
+ */
2
7
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
9
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
package/dist/registry.js CHANGED
@@ -28,6 +28,7 @@ import { support, supportSchema } from "./tools/support.js";
28
28
  import { atlas, atlasSchema } from "./tools/atlas.js";
29
29
  import { reference, referenceSchema } from "./tools/reference.js";
30
30
  import { trilhas, trilhasSchema } from "./tools/trilhas.js";
31
+ import { shareDrop, shareDropSchema } from "./tools/shareDrop.js";
31
32
  import { describeConvexError } from "./convexClient.js";
32
33
  /**
33
34
  * REGISTRY compartilhado do sapiens-mcp: o catálogo de tools (descriptions +
@@ -113,7 +114,7 @@ export const TOOLS = {
113
114
  handler: shorts,
114
115
  },
115
116
  sapiens_video: {
116
- description: "Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as Sinapses da sua conta). Sub-action 'create' (recomendada): escolhe modelo + config e gera num call (cria a row + renderiza). Modelos: 'sapiens-video-seedance' (Seedance 2.0, cena+áudio nativo, 4-15s, 480/720/1080p, t2v/i2v), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), 'sapiens-video-kling-motion' (Motion transfer: passa o movimento de um vídeo pra uma imagem, PRECISA de pessoa com tronco visível na imagem E no vídeo), 'sapiens-video-shot-mimic' (Shot Mimic: recria o plano/câmera/cortes de um vídeo de referência como cena nova), 'sapiens-video-omni' (Gemini Omni: texto vira vídeo 10s 720p com áudio nativo; NÃO aceita mídia do user, ignora references/durationSec/resolution; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), audio, aspectRatio. FRAME INICIAL/FINAL POR REFERÊNCIA (recomendado): startImageId/endImageId (id da sua galeria) ou startImageUrl/endImageUrl (url de galeria/Acervo/personagem) — resolvidos server-side igual à imagem, descubra via sapiens_reference. FRAME POR ARQUIVO LOCAL (só no MCP instalado/stdio, não no remoto): startImagePath/endImagePath = caminho absoluto de uma imagem no seu PC (PNG/JPEG/WebP até 8MB); o processo lê o arquivo e sobe como frame inicial/final, igual a subir no gerador do site — 1 imagem inicial + 1 final por vídeo, então pra vários vídeos rode create uma vez por imagem. No remoto use id/url. Alternativa base64: references (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Suporte a frame final varia por modelo. Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}. VITRINE (sem custo): sub-action 'demos' lista os SEUS demo films (kind=demo do Estúdio de Vídeo) com slug + estado de vitrine; sub-action 'showcase' põe/tira um demo (por slug) do mini-cinema da /conectar-claude, com showcaseTag (chip de capacidade) e showcaseOrder (ordem asc). Fluxo: 'demos' pra achar o slug, depois 'showcase' com showcase=true. Só entra na vitrine pública se for a conta da casa. VÍDEOS PROGRAMÁTICOS (ADMIN, sem custo): a mesa do Estúdio de Vídeo (/experimentos/films, tabela videoSpecs, 5 kinds: demo | aula-tour | essay | tipografia-musical | dataviz) opera por aqui sem browser — 'film-list' (todos os kinds; filtros filmKind/filmStatus), 'film-get' (spec inteiro por slug), 'film-upsert' (cria/atualiza por slug, idempotente; spec = objeto JSON no shape do 'Copiar spec' da tela, validação no servidor), 'film-status' (produção por slug: filmStatus + videoUrl + durationSecMeasured; o fecho do render é os três num call), 'film-publish' (Acervo aba Fitas + portfólio; exige pronto+URL), 'film-delete' (limpar rascunho). O RENDER do filme segue no agente local (skill /film, repo da casa): o MCP registra e fecha o ciclo, não renderiza. create é ASSÍNCRONA: cria o row, debita e volta NA HORA com {imageId, status:'rendering', cost} (não espera o render, que leva de segundos a minutos). Acompanhe com a sub-action 'status' (imageId) até status='completed' (traz a url) ou 'error'/'blocked'. NÃO chame create de novo enquanto renderiza (cria outro vídeo e cobra de novo); falha de provider refunda sozinha. SOM: 'sonorize' (imageId de vídeo SEU completed + prompt do som da cena) gera uma VARIANTE nova com trilha sincronizada (20 Sinapses/s, o original fica intacto; sonorize sempre o original, nunca uma variante). ADMIN: 'shadows' (videoUrl + title) extrai a sombra/depth-map de um vídeo pro Acervo como driving reutilizável; 'shadows-list' lista as sombras prontas. Sub-action 'models' (sem custo, sem login): lista os modelos de vídeo ativos + preço-piso + config (durações/resoluções) + disponibilidade (Omni depende de env).",
117
+ description: "Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as Sinapses da sua conta). Sub-action 'create' (recomendada): escolhe modelo + config e gera num call (cria a row + renderiza). Modelos: 'sapiens-video-seedance' (Seedance 2.0, cena+áudio nativo, 4-15s, 480/720/1080p, t2v/i2v; aceita até 4 imagens de REFERÊNCIA via referenceImageIds/referenceImageUrls/referenceImagePaths (os Veo fast/quality também aceitam, até 3; Lite/Kling/WAN/Omni não), que guiam estilo/personagem/composição SEM virar o 1º frame — é o fluxo STORYBOARD: gere a folha de key poses com sapiens_image templateSlug='storyboard-sapiens-v1', passe folha + personagem como refs num t2v e descreva o take contínuo no prompt, citando as refs por descrição e mandando ignorar o traço do sketch; aceita também 1 VÍDEO DE MOVIMENTO via referenceVideoUrls (role 'refvideo' -> reference_videos, <=15s, host da casa): a coreografia/câmera do clipe guia o take, combinável com a folha), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), 'sapiens-video-kling-motion' (Motion transfer: passa o movimento de um vídeo pra uma imagem, PRECISA de pessoa com tronco visível na imagem E no vídeo), 'sapiens-video-shot-mimic' (Shot Mimic: recria o plano/câmera/cortes de um vídeo de referência como cena nova), 'sapiens-video-omni' (Gemini Omni: texto vira vídeo 10s 720p com áudio nativo; NÃO aceita mídia do user, ignora references/durationSec/resolution; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), audio, aspectRatio. FRAME INICIAL/FINAL POR REFERÊNCIA (recomendado): startImageId/endImageId (id da sua galeria) ou startImageUrl/endImageUrl (url de galeria/Acervo/personagem) — resolvidos server-side igual à imagem, descubra via sapiens_reference. FRAME POR ARQUIVO LOCAL (só no MCP instalado/stdio, não no remoto): startImagePath/endImagePath = caminho absoluto de uma imagem no seu PC (PNG/JPEG/WebP até 8MB); o processo lê o arquivo e sobe como frame inicial/final, igual a subir no gerador do site — 1 imagem inicial + 1 final por vídeo, então pra vários vídeos rode create uma vez por imagem. No remoto use id/url. Alternativa base64: references (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Suporte a frame final varia por modelo. Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}. VITRINE (sem custo): sub-action 'demos' lista os SEUS demo films (kind=demo do Estúdio de Vídeo) com slug + estado de vitrine; sub-action 'showcase' põe/tira um demo (por slug) do mini-cinema da /conectar-claude, com showcaseTag (chip de capacidade) e showcaseOrder (ordem asc). Fluxo: 'demos' pra achar o slug, depois 'showcase' com showcase=true. Só entra na vitrine pública se for a conta da casa. VÍDEOS PROGRAMÁTICOS (ADMIN, sem custo): a mesa do Estúdio de Vídeo (/experimentos/films, tabela videoSpecs, 5 kinds: demo | aula-tour | essay | tipografia-musical | dataviz) opera por aqui sem browser — 'film-list' (todos os kinds; filtros filmKind/filmStatus), 'film-get' (spec inteiro por slug), 'film-upsert' (cria/atualiza por slug, idempotente; spec = objeto JSON no shape do 'Copiar spec' da tela, validação no servidor), 'film-status' (produção por slug: filmStatus + videoUrl + durationSecMeasured; o fecho do render é os três num call), 'film-publish' (Acervo aba Fitas + portfólio; exige pronto+URL), 'film-delete' (limpar rascunho). O RENDER do filme segue no agente local (skill /film, repo da casa): o MCP registra e fecha o ciclo, não renderiza. create é ASSÍNCRONA: cria o row, debita e volta NA HORA com {imageId, status:'rendering', cost} (não espera o render, que leva de segundos a minutos). Acompanhe com a sub-action 'status' (imageId) até status='completed' (traz a url) ou 'error'/'blocked'. NÃO chame create de novo enquanto renderiza (cria outro vídeo e cobra de novo); falha de provider refunda sozinha. SOM: 'sonorize' (imageId de vídeo SEU completed + prompt do som da cena) gera uma VARIANTE nova com trilha sincronizada (20 Sinapses/s, o original fica intacto; sonorize sempre o original, nunca uma variante). ADMIN: 'shadows' (videoUrl + title) extrai a sombra/depth-map de um vídeo pro Acervo como driving reutilizável; 'shadows-list' lista as sombras prontas. Sub-action 'models' (sem custo, sem login): lista os modelos de vídeo ativos + preço-piso + config (durações/resoluções) + disponibilidade (Omni depende de env).",
117
118
  schema: videoSchema,
118
119
  handler: video,
119
120
  },
@@ -173,7 +174,7 @@ export const TOOLS = {
173
174
  handler: atlas,
174
175
  },
175
176
  sapiens_reference: {
176
- description: "O 'popup global de referência' do Sapiens — espelha o modal 'Selecionar Referência' do gerador web: um lugar só pra navegar os bancos e pegar o que vira referência em imagem/vídeo. READ-ONLY. Sub-action 'browse' + bucket: 'history' (suas imagens recentes, privadas+públicas), 'favorites' (imagens que você curtiu, só as suas), 'videos' (seus vídeos / Meus Vídeos), 'stock_video' (banco de B-roll da casa, público), 'acervo' (stock + comunidade públicos; aceita term=busca e source=all|stock|community), 'characters' (personagens; mode=mine [default, inclui rascunhos] ou public [Explorar]). Paginado (page/limit, default 20, máx 50; use hasMore). Itens normalizados: imagem PRÓPRIA (history/favorites) traz imageId + url (use imageId em sapiens_image sourceImageIds ou sapiens_video startImageId/endImageId; ou a url em referenceImageUrls); acervo e characters são públicos/de terceiros, use a url (characters trazem mainImageUrl + imageUrls + characterId) em referenceImageUrls / startImageUrl / endImageUrl, NÃO em sourceImageIds. Personagens públicos também têm porta dedicada em sapiens_character action=list_public.",
177
+ description: "O 'popup global de referência' do Sapiens — espelha o modal 'Selecionar Referência' do gerador web: um lugar só pra navegar os bancos e pegar o que vira referência em imagem/vídeo. READ-ONLY. DUAS sub-actions: 'browse' (navega um banco) e 'resolve' (acha UM asset pelo ID citável). RESOLVE: passe handle=<tipo>_<id> (img_… imagem, vid_… vídeo, film_… vídeo programático, trk_… música, comic_… tirinha, char_… personagem — é o ID que o usuário copia no detalhe do item) e o servidor devolve {mine, isPublic, url, title, mediaKind} SE for seu ou público (senão recusa; nunca acessa asset privado de terceiro). Use a url retornada como referência (referenceImageUrls / startImageUrl / endImageUrl). BROWSE + bucket: 'history' (suas imagens recentes, privadas+públicas), 'favorites' (imagens que você curtiu, só as suas), 'videos' (seus vídeos / Meus Vídeos), 'stock_video' (banco de B-roll da casa, público), 'acervo' (stock + comunidade públicos; aceita term=busca e source=all|stock|community), 'characters' (personagens; mode=mine [default, inclui rascunhos] ou public [Explorar]). Paginado (page/limit, default 20, máx 50; use hasMore). Itens normalizados: imagem PRÓPRIA (history/favorites) traz imageId + url (use imageId em sapiens_image sourceImageIds ou sapiens_video startImageId/endImageId; ou a url em referenceImageUrls); acervo e characters são públicos/de terceiros, use a url (characters trazem mainImageUrl + imageUrls + characterId) em referenceImageUrls / startImageUrl / endImageUrl, NÃO em sourceImageIds. Personagens públicos também têm porta dedicada em sapiens_character action=list_public.",
177
178
  schema: referenceSchema,
178
179
  handler: reference,
179
180
  },
@@ -182,6 +183,11 @@ export const TOOLS = {
182
183
  schema: trilhasSchema,
183
184
  handler: trilhas,
184
185
  },
186
+ sapiens_share: {
187
+ description: "Share Drop (ADMIN-ONLY): sobe um HTML single-file (doctype + CSS/JS inline) e recebe o link curto sapiens.app/s/<code> pra mandar pra alguém abrir no navegador sem instalar nada. O PONTO: publicar um artefato e depois RE-PUBLICAR no MESMO link quantas vezes quiser (action=update com o code) — edita o HTML aqui no Claude, joga pro ar, atualiza o mesmo arquivo. Sub-actions: 'list' (links ativos: code + url + título + tamanho + isActive/hasPassword/requireLogin), 'upload' (cria link NOVO a partir de html ou htmlPath → devolve {code, url}), 'update' (re-publica no MESMO code: mesma url, conteúdo novo, apaga o blob antigo), 'delete' (tira do ar — o link vira 404), 'settings' (isActive liga/desliga, password senha string vazia remove, requireLogin exige conta logada). O HTML entra por `html` (conteúdo string, funciona no stdio e no remoto) OU `htmlPath` (caminho absoluto de um .html local, SÓ no MCP instalado/stdio). Título sai do <title> do HTML se não passar `title` (ou keepTitle=true no update pra manter o atual). Sem custo em Sinapses. Espelha a mesa /dashboard/admin/share-drop.",
188
+ schema: shareDropSchema,
189
+ handler: shareDrop,
190
+ },
185
191
  };
186
192
  // Guia de uso server-level: o protocolo MCP devolve isto no handshake (initialize)
187
193
  // e TODO cliente (Helen no Hermes, Gemini CLI, Cursor, Antigravity) injeta no modelo.
@@ -279,6 +285,7 @@ const TOOL_TITLES = {
279
285
  sapiens_reference: "Referências",
280
286
  sapiens_trilhas: "Trilhas & Desafios",
281
287
  sapiens_instagram: "Auto-DM Instagram",
288
+ sapiens_share: "Share Drop",
282
289
  };
283
290
  // Tools ADMIN-ONLY de ponta a ponta (o Convex recusa user comum em toda action):
284
291
  // escondidas do tools/list quando o servidor SABE que o tier é user. São as
@@ -293,6 +300,7 @@ const ADMIN_ONLY_TOOLS = new Set([
293
300
  "sapiens_shorts",
294
301
  "sapiens_instagram",
295
302
  "sapiens_aula",
303
+ "sapiens_share",
296
304
  ]);
297
305
  /**
298
306
  * Monta o payload do tools/list pro tier dado. Tier "user" esconde as
package/dist/remote.js CHANGED
@@ -259,7 +259,13 @@ export function createSapiensRemoteHandler(opts) {
259
259
  clientId: "sapiens-remote",
260
260
  scopes: [],
261
261
  };
262
- }, { required: true });
262
+ }, {
263
+ required: true,
264
+ // Habilita o discovery OAuth no 401 (RFC 9728) quando o app configura o
265
+ // PRM. O access_token é o mesmo sessionToken bearer (o OAuth só o emite).
266
+ resourceMetadataPath: opts.resourceMetadataPath,
267
+ resourceUrl: opts.resourceUrl,
268
+ });
263
269
  return async (req) => {
264
270
  const ip = clientIp(req);
265
271
  if (!ipLimiter.hit(ip)) {
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { convexAction, getSessionToken } from "../convexClient.js";
2
+ import { convexAction, convexQuery, getSessionToken } from "../convexClient.js";
3
3
  /**
4
4
  * sapiens_reference — o "popup global de referência" do Sapiens via MCP.
5
5
  *
@@ -24,13 +24,21 @@ import { convexAction, getSessionToken } from "../convexClient.js";
24
24
  */
25
25
  export const referenceSchema = z.object({
26
26
  action: z
27
- .enum(["browse"])
28
- .describe("Só 'browse' por enquanto: navega um bucket do acervo."),
27
+ .enum(["browse", "resolve"])
28
+ .describe("'browse' navega um bucket do acervo; 'resolve' acha UM asset pelo ID citável (handle <tipo>_<id>) " +
29
+ "e diz se é seu / público + devolve a url pra usar de referência."),
29
30
  bucket: z
30
31
  .enum(["history", "favorites", "videos", "stock_video", "acervo", "characters"])
31
- .describe("Qual banco navegar: 'history' (suas imagens recentes), 'favorites' (as que você curtiu), " +
32
+ .optional()
33
+ .describe("Só 'browse': qual banco navegar: 'history' (suas imagens recentes), 'favorites' (as que você curtiu), " +
32
34
  "'videos' (seus vídeos), 'stock_video' (Banco de Vídeo da casa: clipes/B-roll prontos, aceita term/orientation/loopOnly), " +
33
35
  "'acervo' (stock + comunidade públicos de IMAGEM), 'characters' (personagens)."),
36
+ handle: z
37
+ .string()
38
+ .optional()
39
+ .describe("Só 'resolve': o ID citável do asset, formato <tipo>_<id> — img_… (imagem), vid_… (vídeo), " +
40
+ "film_… (vídeo programático), trk_… (música), comic_… (tirinha), char_… (personagem). " +
41
+ "É o ID que o usuário copia no detalhe do item."),
34
42
  page: z
35
43
  .number()
36
44
  .int()
@@ -67,7 +75,31 @@ export const referenceSchema = z.object({
67
75
  });
68
76
  export async function reference(args) {
69
77
  const sessionToken = getSessionToken();
78
+ if (args.action === "resolve") {
79
+ if (!args.handle) {
80
+ return {
81
+ error: "resolve exige `handle` (ex: img_..., vid_..., trk_...). É o ID que o usuário copia no detalhe do item.",
82
+ };
83
+ }
84
+ // resolveHandle é QUERY (read-only): posse + alcance server-side.
85
+ const res = await convexQuery("assets:resolveHandle", {
86
+ sessionToken,
87
+ handle: args.handle,
88
+ });
89
+ return {
90
+ ...res,
91
+ note: "Asset resolvido por posse + alcance. `mine`=seu, `isPublic`=público (seu ou de terceiro). " +
92
+ "Use a `url` como referência: em sapiens_image passe em referenceImageUrls; em sapiens_video em " +
93
+ "startImageUrl/endImageUrl (a url da casa já é allowlistada). Se não for seu nem público, o servidor " +
94
+ "recusa — não force.",
95
+ };
96
+ }
70
97
  if (args.action === "browse") {
98
+ if (!args.bucket) {
99
+ return {
100
+ error: "browse exige `bucket` (history | favorites | videos | stock_video | acervo | characters).",
101
+ };
102
+ }
71
103
  const res = await convexAction("mcpReferences:referenceBrowse", {
72
104
  sessionToken,
73
105
  bucket: args.bucket,
@@ -0,0 +1,163 @@
1
+ import { z } from "zod";
2
+ import { readFile } from "node:fs/promises";
3
+ import { convexQuery, convexMutation, getSessionToken, isRemoteContext, } from "../convexClient.js";
4
+ import { need } from "../schema.js";
5
+ /**
6
+ * sapiens_share — Share Drop pelo MCP (ADMIN-ONLY; requireMcpAdmin no Convex).
7
+ *
8
+ * Sobe um HTML single-file e recebe o link curto sapiens.app/s/<code>. O ponto:
9
+ * PUBLICAR um artefato e depois RE-PUBLICAR no MESMO link quantas vezes quiser
10
+ * (action=update com o code) — edita aqui no Claude, joga pro ar, atualiza o
11
+ * mesmo arquivo. Espelha a mesa /dashboard/admin/share-drop e o CLI
12
+ * share-drop-sync, mas sem admin key: identidade vem do sessionToken.
13
+ *
14
+ * O HTML entra por `html` (string, funciona no stdio E no remoto) ou por
15
+ * `htmlPath` (caminho local .html, SÓ no MCP instalado/stdio — no remoto não há
16
+ * disco do usuário). Título sai do <title> do HTML se não vier explícito.
17
+ */
18
+ // Domínio que serve a rota /s/[code]. No browser a UI usa window.location.origin
19
+ // (o site: sapiensinteticos.com); aqui no server-side usamos o domínio canônico
20
+ // do site, com override por env. NÃO usar "sapiens.app": aquilo é só texto
21
+ // ilustrativo na página do Share Drop, não é um domínio configurado.
22
+ const SHARE_BASE_URL = process.env.SHARE_DROP_BASE_URL?.replace(/\/$/, "") ||
23
+ "https://sapiensinteticos.com";
24
+ export const shareDropSchema = z.object({
25
+ action: z.enum(["list", "upload", "update", "delete", "settings"]),
26
+ code: z
27
+ .string()
28
+ .optional()
29
+ .describe("O shortcode do link (ex: dpcprvfa). Obrigatório pra update, delete e settings."),
30
+ html: z
31
+ .string()
32
+ .optional()
33
+ .describe("O HTML single-file COMPLETO (doctype + CSS/JS inline). Use pra upload/update. Funciona no stdio e no remoto."),
34
+ htmlPath: z
35
+ .string()
36
+ .optional()
37
+ .describe("Caminho ABSOLUTO de um .html local pra subir (upload/update). SÓ no MCP instalado (stdio); no remoto use `html`."),
38
+ title: z
39
+ .string()
40
+ .optional()
41
+ .describe("Título mostrado no admin. Se omitido, extrai do <title> do HTML."),
42
+ keepTitle: z
43
+ .boolean()
44
+ .optional()
45
+ .describe("update: mantém o título atual do link (não atualiza pelo HTML)."),
46
+ isActive: z
47
+ .boolean()
48
+ .optional()
49
+ .describe("settings: liga/desliga o link (desligado responde 'indisponível')."),
50
+ password: z
51
+ .string()
52
+ .optional()
53
+ .describe("settings: senha pra ver o conteúdo (string vazia remove a senha)."),
54
+ requireLogin: z
55
+ .boolean()
56
+ .optional()
57
+ .describe("settings: exige conta Sapiens logada pra abrir o link."),
58
+ });
59
+ /** Extrai o <title> do HTML (mesma regex do client/CLI). */
60
+ function extractTitle(html) {
61
+ const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
62
+ if (m && m[1]) {
63
+ const cleaned = m[1].replace(/\s+/g, " ").trim();
64
+ if (cleaned)
65
+ return cleaned;
66
+ }
67
+ return undefined;
68
+ }
69
+ /** Resolve o HTML de `html` (string) ou `htmlPath` (arquivo local, stdio only). */
70
+ async function resolveHtml(args) {
71
+ if (typeof args.html === "string" && args.html.trim()) {
72
+ return args.html;
73
+ }
74
+ if (args.htmlPath) {
75
+ if (isRemoteContext()) {
76
+ throw new Error("htmlPath só funciona no MCP instalado (stdio). No remoto, mande o conteúdo em `html`.");
77
+ }
78
+ return await readFile(args.htmlPath, "utf8");
79
+ }
80
+ throw new Error("Falta o HTML: passe `html` (o conteúdo) ou `htmlPath` (caminho de um .html local).");
81
+ }
82
+ /** Sobe o HTML pro storage via signed URL e devolve o storageId + tamanho. */
83
+ async function uploadBlob(sessionToken, html) {
84
+ const uploadUrl = await convexMutation("mcpExtras:mcpGenerateShareUploadUrl", { sessionToken });
85
+ const res = await fetch(uploadUrl, {
86
+ method: "POST",
87
+ headers: { "Content-Type": "text/html" },
88
+ body: html,
89
+ });
90
+ if (!res.ok) {
91
+ throw new Error(`Upload pro storage falhou: HTTP ${res.status} ${res.statusText}`);
92
+ }
93
+ const body = (await res.json());
94
+ if (!body.storageId)
95
+ throw new Error("Upload não retornou storageId.");
96
+ return { storageId: body.storageId, sizeBytes: Buffer.byteLength(html, "utf8") };
97
+ }
98
+ export async function shareDrop(args) {
99
+ const sessionToken = getSessionToken();
100
+ switch (args.action) {
101
+ case "list": {
102
+ const rows = await convexQuery("mcpExtras:mcpListShareDrops", {
103
+ sessionToken,
104
+ });
105
+ return {
106
+ count: rows.length,
107
+ links: rows.map((r) => ({ ...r, url: `${SHARE_BASE_URL}/s/${r.code}` })),
108
+ };
109
+ }
110
+ case "upload": {
111
+ const html = await resolveHtml(args);
112
+ const title = args.title ?? extractTitle(html) ?? "Untitled";
113
+ const { storageId, sizeBytes } = await uploadBlob(sessionToken, html);
114
+ const res = await convexMutation("mcpExtras:mcpCreateShareDrop", { sessionToken, storageId, title, sizeBytes, contentType: "text/html" });
115
+ return {
116
+ code: res.code,
117
+ title: res.title,
118
+ url: `${SHARE_BASE_URL}/s/${res.code}`,
119
+ sizeBytes,
120
+ };
121
+ }
122
+ case "update": {
123
+ const code = need(args.code, "code");
124
+ const html = await resolveHtml(args);
125
+ const newTitle = args.keepTitle
126
+ ? undefined
127
+ : args.title ?? extractTitle(html);
128
+ const { storageId, sizeBytes } = await uploadBlob(sessionToken, html);
129
+ const res = await convexMutation("mcpExtras:mcpReplaceShareDrop", {
130
+ sessionToken,
131
+ code,
132
+ newStorageId: storageId,
133
+ sizeBytes,
134
+ contentType: "text/html",
135
+ newTitle,
136
+ });
137
+ return {
138
+ code: res.code,
139
+ title: res.title,
140
+ url: `${SHARE_BASE_URL}/s/${res.code}`,
141
+ sizeBytes: res.sizeBytes,
142
+ updatedAt: res.updatedAt,
143
+ };
144
+ }
145
+ case "delete": {
146
+ const code = need(args.code, "code");
147
+ return await convexMutation("mcpExtras:mcpDeleteShareDrop", {
148
+ sessionToken,
149
+ code,
150
+ });
151
+ }
152
+ case "settings": {
153
+ const code = need(args.code, "code");
154
+ return await convexMutation("mcpExtras:mcpSetShareDropSettings", {
155
+ sessionToken,
156
+ code,
157
+ isActive: args.isActive,
158
+ password: args.password,
159
+ requireLogin: args.requireLogin,
160
+ });
161
+ }
162
+ }
163
+ }
@@ -37,7 +37,11 @@ import { httpUrl } from "../schema.js";
37
37
  * - film-delete: apaga um spec (limpar rascunho/duplicata).
38
38
  *
39
39
  * Modelos (action=create):
40
- * - sapiens-video-seedance Seedance 2.0 — cena com áudio nativo, 4-15s, 480/720/1080p (t2v/i2v)
40
+ * - sapiens-video-seedance Seedance 2.0 — cena com áudio nativo, 4-15s, 480/720/1080p (t2v/i2v).
41
+ * Aceita até 4 imagens de REFERÊNCIA (referenceImage*): guiam estilo/
42
+ * personagem/composição sem virar o 1º frame. É o fluxo storyboard:
43
+ * folha de key poses (template storyboard-sapiens-v1 do sapiens_image)
44
+ * + imagem do personagem como refs num t2v de take contínuo.
41
45
  * - sapiens-video-kling Kling 3.0 Pro — dá vida a uma imagem, 3-15s, sound opcional (i2v/t2v)
42
46
  * - sapiens-video-wan WAN 2.5 — imagem que fala/canta (áudio+lip-sync nativo), 5/10s (i2v)
43
47
  * - sapiens-video-kling-motion Kling Motion — transfere o movimento de um vídeo pra uma imagem
@@ -223,6 +227,29 @@ export const videoSchema = z.object({
223
227
  endImageUrl: httpUrl()
224
228
  .optional()
225
229
  .describe("Frame FINAL: url pública de galeria/Acervo/personagem. Vira reference role 'end' (suporte varia por modelo)."),
230
+ // Imagens de REFERÊNCIA (Seedance reference_images): guiam estilo/personagem/
231
+ // composição SEM virar o 1º frame. Fluxo storyboard: folha de key poses +
232
+ // personagem como refs num text-to-video.
233
+ referenceImageIds: z
234
+ .array(z.string())
235
+ .optional()
236
+ .describe("action=create (Seedance até 4; Veo fast/quality até 3; Lite/Kling/WAN/Omni NÃO): generatedImages:_id da SUA galeria " +
237
+ "como imagens de REFERÊNCIA (role 'ref'). Diferente de startImageId: NÃO viram o 1º frame, guiam " +
238
+ "estilo/personagem/composição. Fluxo storyboard: gere a folha com sapiens_image " +
239
+ "templateSlug='storyboard-sapiens-v1' (ou -vertical-v1) e passe folha + personagem aqui."),
240
+ referenceImageUrls: z
241
+ .array(httpUrl())
242
+ .optional()
243
+ .describe("action=create (Seedance até 4; Veo fast/quality até 3): URLs públicas (Bunny/Convex/Wikimedia) como imagens de " +
244
+ "REFERÊNCIA (role 'ref'). Soma com referenceImageIds. No prompt, cite as referências por descrição " +
245
+ "(ex: 'use the storyboard reference as ordered key poses; ignore line-sketch artifacts')."),
246
+ referenceVideoUrls: z
247
+ .array(httpUrl())
248
+ .optional()
249
+ .describe("action=create (só Seedance): 1 URL pública (host da casa) de um VÍDEO DE MOVIMENTO (role 'refvideo' -> reference_videos, <=15s): " +
250
+ "a coreografia/câmera do clipe GUIA o take sem ser recriado plano a plano (isso é o Shot Mimic). " +
251
+ "Combina com o fluxo storyboard: folha = beats, vídeo = movimento, personagem = identidade. " +
252
+ "Sombras do banco de motion servem direto (ADMIN: descubra com action=shadows-list)."),
226
253
  // Frame inicial/final por ARQUIVO LOCAL (paridade com o upload do gerador do
227
254
  // site). Só no MCP instalado (stdio): o processo lê o arquivo do disco e sobe
228
255
  // como reference role 'start'/'end', sem o base64 passar pelo contexto do
@@ -237,6 +264,11 @@ export const videoSchema = z.object({
237
264
  .string()
238
265
  .optional()
239
266
  .describe("Frame FINAL a partir de um ARQUIVO LOCAL do seu PC — só no MCP instalado (stdio). Caminho absoluto; PNG/JPEG/WebP até 8MB. Vira reference role 'end' (suporte varia por modelo). 1 imagem por vídeo. Mutuamente exclusivo com endImageId/endImageUrl."),
267
+ referenceImagePaths: z
268
+ .array(z.string())
269
+ .optional()
270
+ .describe("action=create (só Seedance): imagens de REFERÊNCIA a partir de ARQUIVOS LOCAIS do seu PC — só no MCP instalado (stdio). " +
271
+ "Caminhos absolutos; PNG/JPEG/WebP até 8MB cada, até 4 no total (somando com referenceImageIds/Urls). Viram reference role 'ref'."),
240
272
  });
241
273
  // Teto do arquivo local que vira frame de vídeo. Frame inicial/final não precisa
242
274
  // ser pesado; 8MB cobre um PNG/JPEG grande com folga e evita estourar o payload
@@ -306,6 +338,11 @@ export async function localFrameReferences(args) {
306
338
  }
307
339
  refs.push(await readLocalImageAsReference(args.endImagePath, "end"));
308
340
  }
341
+ // Referências (role 'ref', Seedance): arquivos locais somam com ids/urls; o
342
+ // teto total (4) é validado no servidor junto do resto.
343
+ for (const p of args.referenceImagePaths ?? []) {
344
+ refs.push(await readLocalImageAsReference(p, "ref"));
345
+ }
309
346
  return refs;
310
347
  }
311
348
  export async function video(args) {
@@ -464,6 +501,9 @@ export async function video(args) {
464
501
  startImageUrl: args.startImageUrl,
465
502
  endImageId: args.endImageId,
466
503
  endImageUrl: args.endImageUrl,
504
+ referenceImageIds: args.referenceImageIds,
505
+ referenceImageUrls: args.referenceImageUrls,
506
+ referenceVideoUrls: args.referenceVideoUrls,
467
507
  editOfImageId: args.editOfImageId,
468
508
  });
469
509
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.38.0",
3
+ "version": "1.40.0",
4
4
  "description": "MCP server pra operar o Sapiens Sintéticos (sapiensinteticos.com) pelo Claude Code: gerar imagem, escrever artigo, voz, música e mais, na sua conta. Login pelo código de sapiensinteticos.com/conectar-claude.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,6 +28,7 @@
28
28
  "image-generation"
29
29
  ],
30
30
  "license": "MIT",
31
+ "author": "BorderLess (Sapiens Sintéticos), sapiensinteticos.com",
31
32
  "scripts": {
32
33
  "build": "tsc",
33
34
  "dev": "tsx src/index.ts",