sapiens-mcp 1.28.0 → 1.29.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 +7 -0
- package/dist/convexClient.js +46 -5
- package/dist/index.js +60 -3
- package/dist/schema.js +52 -0
- package/dist/tools/article.js +3 -3
- package/dist/tools/character.js +2 -2
- package/dist/tools/forum.js +3 -4
- package/dist/tools/image.js +3 -3
- package/dist/tools/meta.js +4 -18
- package/dist/tools/video.js +4 -6
- package/dist/tools/write.js +2 -2
- package/dist/version.js +26 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -36,6 +36,13 @@ O token de 30 dias fica salvo em `~/.sapiens-mcp/session.json`. Pra sair: `sapie
|
|
|
36
36
|
|
|
37
37
|
O Claude avisa o custo antes de gastar, e geração que falha é estornada. Publicar no blog editorial, Coluna Sapiens e o pipeline são exclusivos do dono da plataforma.
|
|
38
38
|
|
|
39
|
+
## Troubleshooting
|
|
40
|
+
|
|
41
|
+
- **"sessionToken expirado" / "Conta Sapiens não conectada".** O token de 30 dias venceu ou nunca foi salvo. Abra [sapiensinteticos.com/conectar-claude](https://www.sapiensinteticos.com/conectar-claude) logado, gere um código novo e rode `sapiens_meta action=login code=XXXX-XXXX`.
|
|
42
|
+
- **Ferramenta some ou some capacidade nova depois de atualizar.** O client roda via `npx -y sapiens-mcp` (sem versão fixa) e pode ter ficado preso num cache antigo. Confira o que está rodando com `sapiens_meta action=version` (mostra a versão do binário + a última do npm + `upToDate`). Se `upToDate:false`, limpe o cache do npx e reinicie o client.
|
|
43
|
+
- **"Argumentos inválidos".** A mensagem já diz qual campo faltou ou saiu errado; refaça a chamada com o que ela pede. Não repita a mesma chamada que falhou (3 falhas seguidas fazem o client marcar o servidor como indisponível por ~1 min, um disjuntor anti-loop).
|
|
44
|
+
- **Saldo baixo antes de gerar.** `sapiens_meta action=credits` (ou `action=subscription` pro detalhe por bucket) mostra quanto sobra antes de gastar em imagem/música/vídeo.
|
|
45
|
+
|
|
39
46
|
## Privacidade
|
|
40
47
|
|
|
41
48
|
O servidor só conversa com o backend público do Sapiens (Convex). Sua identidade vem sempre do token de login, nunca de parâmetros soltos. Cada conta só mexe no que é dela.
|
package/dist/convexClient.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConvexHttpClient } from "convex/browser";
|
|
2
|
+
import { ZodError } from "zod";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
@@ -107,11 +108,22 @@ function sessionStorePath() {
|
|
|
107
108
|
}
|
|
108
109
|
export function saveSessionToken(token) {
|
|
109
110
|
const file = sessionStorePath();
|
|
110
|
-
|
|
111
|
+
// mode 0700/0600: o arquivo guarda um bearer de 30 dias. Sem isto, em POSIX o
|
|
112
|
+
// dir sai 0755 e o arquivo 0644 (world-readable) — num host compartilhado (a
|
|
113
|
+
// VPS da Helen) outro usuário local leria o token e assumiria a conta. mode só
|
|
114
|
+
// aplica na CRIAÇÃO, então o chmod explícito cobre o caso de reescrever um
|
|
115
|
+
// arquivo já existente 0644. No Windows chmod é no-op benigno (try/catch).
|
|
116
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
111
117
|
// Espelha os 30 dias do server (só pra avisar quando perto de expirar; a
|
|
112
118
|
// validade real é sempre checada no Convex).
|
|
113
119
|
const expiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000;
|
|
114
|
-
fs.writeFileSync(file, JSON.stringify({ sessionToken: token, expiresAt }, null, 2), "utf8");
|
|
120
|
+
fs.writeFileSync(file, JSON.stringify({ sessionToken: token, expiresAt }, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
121
|
+
try {
|
|
122
|
+
fs.chmodSync(file, 0o600);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Windows/ACL: chmod pode não aplicar; o alvo real é o POSIX da VPS.
|
|
126
|
+
}
|
|
115
127
|
return { path: file, expiresAt };
|
|
116
128
|
}
|
|
117
129
|
export function clearSessionToken() {
|
|
@@ -198,6 +210,16 @@ export function getSessionToken() {
|
|
|
198
210
|
* pelo handler global (index.ts) quanto pelos catches locais (ex: meta health).
|
|
199
211
|
*/
|
|
200
212
|
export function describeConvexError(e) {
|
|
213
|
+
// Erro de validação de argumento (schema.parse no dispatch): sem isto o
|
|
214
|
+
// ZodError caía cru (e.message = dump JSON das issues). Vira uma linha
|
|
215
|
+
// legível e acionável pro modelo refazer a chamada com o campo certo.
|
|
216
|
+
if (e instanceof ZodError) {
|
|
217
|
+
const parts = e.issues.map((i) => {
|
|
218
|
+
const at = i.path.length ? i.path.join(".") : "(raiz)";
|
|
219
|
+
return `${at}: ${i.message}`;
|
|
220
|
+
});
|
|
221
|
+
return `Argumentos inválidos: ${parts.join("; ")}`;
|
|
222
|
+
}
|
|
201
223
|
const data = e?.data;
|
|
202
224
|
if (typeof data === "string" && data.trim())
|
|
203
225
|
return data;
|
|
@@ -214,15 +236,34 @@ export function describeConvexError(e) {
|
|
|
214
236
|
}
|
|
215
237
|
return e?.message ?? String(e);
|
|
216
238
|
}
|
|
239
|
+
// Teto de tempo por chamada ao Convex. O ConvexHttpClient não aceita
|
|
240
|
+
// AbortSignal em query/mutation/action, então o bound prático é um race contra
|
|
241
|
+
// um timer. Sem isto, um backend pendurado deixa a chamada MCP presa até o
|
|
242
|
+
// timeout do host, sem erro claro — ruim numa sessão que acabou de mandar
|
|
243
|
+
// debitar Sinapses e não sabe se caiu. NÃO há retry/backoff de propósito:
|
|
244
|
+
// chamadas não-idempotentes (cobrança) não podem re-disparar às cegas.
|
|
245
|
+
const CONVEX_TIMEOUT_MS = Number(process.env.SAPIENS_CONVEX_TIMEOUT_MS) || 30000;
|
|
246
|
+
function withTimeout(p, label) {
|
|
247
|
+
let timer;
|
|
248
|
+
const timeout = new Promise((_, reject) => {
|
|
249
|
+
timer = setTimeout(() => {
|
|
250
|
+
reject(new Error(`Timeout: o backend Sapiens não respondeu em ${Math.round(CONVEX_TIMEOUT_MS / 1000)}s (${label}). Tente de novo em instantes.`));
|
|
251
|
+
}, CONVEX_TIMEOUT_MS);
|
|
252
|
+
});
|
|
253
|
+
return Promise.race([
|
|
254
|
+
p.finally(() => clearTimeout(timer)),
|
|
255
|
+
timeout,
|
|
256
|
+
]);
|
|
257
|
+
}
|
|
217
258
|
export async function convexQuery(fnPath, args) {
|
|
218
259
|
const client = getConvex();
|
|
219
|
-
return (await client.query(fnPath, args));
|
|
260
|
+
return (await withTimeout(client.query(fnPath, args), `query ${fnPath}`));
|
|
220
261
|
}
|
|
221
262
|
export async function convexMutation(fnPath, args) {
|
|
222
263
|
const client = getConvex();
|
|
223
|
-
return (await client.mutation(fnPath, args));
|
|
264
|
+
return (await withTimeout(client.mutation(fnPath, args), `mutation ${fnPath}`));
|
|
224
265
|
}
|
|
225
266
|
export async function convexAction(fnPath, args) {
|
|
226
267
|
const client = getConvex();
|
|
227
|
-
return (await client.action(fnPath, args));
|
|
268
|
+
return (await withTimeout(client.action(fnPath, args), `action ${fnPath}`));
|
|
228
269
|
}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import { atlas, atlasSchema } from "./tools/atlas.js";
|
|
|
33
33
|
import { reference, referenceSchema } from "./tools/reference.js";
|
|
34
34
|
import { trilhas, trilhasSchema } from "./tools/trilhas.js";
|
|
35
35
|
import { describeConvexError } from "./convexClient.js";
|
|
36
|
+
import { MCP_VERSION } from "./version.js";
|
|
36
37
|
const TOOLS = {
|
|
37
38
|
sapiens_pipeline: {
|
|
38
39
|
description: "CRUD do content pipeline Sapiens (sources/productions/publishables). Sub-actions: list_sources, list_articles (use includeDrafts pra incluir drafts; onlyAvailable pra esconder os já virados em source), get_source, get_production, list_versions, add_article_as_source, create_draft_article_and_source (seed), create_production (sourceId+format → productionId draft), update_production (substitui payload, opcionalmente muda status), finalize_production (cria publishable v1, v2... com snapshot), remove_production, remove_source, set_source_done, update_source_notes, restore_version (volta payload duma versão antiga), propose_mega_grafico_plan (granular: só gera plano via Gemini, devolve fullPrompt+spec), run_mega_grafico_full (ONE-SHOT, recomendado: cria production+propõe plano+gera imagem+aplica selo Sapiens+finaliza publishable numa chamada só), generate_carousel (gera um carrossel editorial standalone a partir de brief OU articleId — 7-9 slides na voz Sapiens + imagens do banco; devolve id + url do editor pro humano abrir, ajustar e exportar; admin-only, cobra Sinapses, reembolsa se falhar). Pra mega_grafico, SEMPRE prefira run_mega_grafico_full em vez de sequenciar manualmente — menos drift, idempotente (passa productionId pra reusar). OBRIGATÓRIO perguntar ao user antes se withHelen=true (cartoon Helen interage com tema, ~15-25% do poster) ou false (poster 100% diagramático). Custo ~900-1000 sinapses por geração. Use skipFinalize=true se quiser deixar production em 'ready' pro admin revisar antes de publishable. Payload livre por formato — chame sapiens_meta action=formats pra ver schemas sugeridos.",
|
|
@@ -110,7 +111,7 @@ const TOOLS = {
|
|
|
110
111
|
handler: shorts,
|
|
111
112
|
},
|
|
112
113
|
sapiens_video: {
|
|
113
|
-
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
|
|
114
|
+
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. 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.",
|
|
114
115
|
schema: videoSchema,
|
|
115
116
|
handler: video,
|
|
116
117
|
},
|
|
@@ -226,12 +227,61 @@ Mídia no Fórum (PADRÃO, faça sempre assim): a peça vai EMBEDADA num card es
|
|
|
226
227
|
Trilhas e Desafios (sapiens_trilhas): 'list'/'get' pra navegar as trilhas; 'list_challenges' mostra os Desafios ativos + seu status; 'claim_mission' (missionId + proofText) envia a prova pra revisão do dono — o crédito em Sinapses sai na aprovação dele, não na hora. Não prometa Sinapse na hora do claim.
|
|
227
228
|
|
|
228
229
|
Voz da casa: 1ª pessoa, direto, anti-corporate, sem travessão. Pra bom entendedor, meia palavra basta.`;
|
|
229
|
-
|
|
230
|
+
// Annotations MCP: título humano + dica read-only. São HINTS (não-confiáveis por
|
|
231
|
+
// spec): quem gateia de verdade continua o servidor (saldo, gate de admin,
|
|
232
|
+
// posse). Servem pro client AUTO-APROVAR leitura pura e PEDIR confirmação em
|
|
233
|
+
// gasto. openWorldHint=true em todas: toda tool fala com o backend remoto do
|
|
234
|
+
// Sapiens. Só marco readOnlyHint nas que NÃO têm nenhuma sub-action que
|
|
235
|
+
// escreve/cobra (as multi-action com 1 mutation ficam de fora, honestamente).
|
|
236
|
+
const READ_ONLY_TOOLS = new Set([
|
|
237
|
+
"sapiens_search",
|
|
238
|
+
"sapiens_stock_audio",
|
|
239
|
+
"sapiens_stock_video",
|
|
240
|
+
"sapiens_atlas",
|
|
241
|
+
"sapiens_reference",
|
|
242
|
+
]);
|
|
243
|
+
const TOOL_TITLES = {
|
|
244
|
+
sapiens_pipeline: "Pipeline de Conteúdo",
|
|
245
|
+
sapiens_image: "Gerar Imagem",
|
|
246
|
+
sapiens_meta: "Conta & Utilitários",
|
|
247
|
+
sapiens_repertorio: "Repertório",
|
|
248
|
+
sapiens_gallery: "Galeria de Imagens",
|
|
249
|
+
sapiens_community: "Chat da Comunidade",
|
|
250
|
+
sapiens_article: "Blog Editorial",
|
|
251
|
+
sapiens_write: "Artigos do Perfil",
|
|
252
|
+
sapiens_quote_pop: "Coluna Sapiens/Repertório",
|
|
253
|
+
sapiens_search: "Buscar Artigos",
|
|
254
|
+
sapiens_studios: "Estúdios & Emancipação",
|
|
255
|
+
sapiens_persona: "Persona (MBTI)",
|
|
256
|
+
sapiens_helen: "Voz Helen (TTS)",
|
|
257
|
+
sapiens_musicator: "Musicator",
|
|
258
|
+
sapiens_shorts: "Sapiens Shorts",
|
|
259
|
+
sapiens_video: "Sapiens Video",
|
|
260
|
+
sapiens_stock_audio: "Banco de Áudio",
|
|
261
|
+
sapiens_stock_video: "Banco de Vídeo",
|
|
262
|
+
sapiens_brand: "Brand (Design System)",
|
|
263
|
+
sapiens_character: "Personagens",
|
|
264
|
+
sapiens_profile: "Perfil",
|
|
265
|
+
sapiens_sintetico: "Sintético & Sintonia",
|
|
266
|
+
sapiens_forum: "Fórum de Ressonância",
|
|
267
|
+
sapiens_aula: "Aulas (Mentoria)",
|
|
268
|
+
sapiens_support: "Suporte",
|
|
269
|
+
sapiens_atlas: "Atlas Ecossistema IA",
|
|
270
|
+
sapiens_reference: "Referências",
|
|
271
|
+
sapiens_trilhas: "Trilhas & Desafios",
|
|
272
|
+
sapiens_instagram: "Auto-DM Instagram",
|
|
273
|
+
};
|
|
274
|
+
const server = new Server({ name: "mcp-sapiens", version: MCP_VERSION }, { capabilities: { tools: {} }, instructions: SAPIENS_INSTRUCTIONS });
|
|
230
275
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
231
276
|
tools: Object.entries(TOOLS).map(([name, t]) => ({
|
|
232
277
|
name,
|
|
233
278
|
description: t.description,
|
|
234
279
|
inputSchema: zodToJsonSchema(t.schema),
|
|
280
|
+
annotations: {
|
|
281
|
+
title: TOOL_TITLES[name] ?? name,
|
|
282
|
+
readOnlyHint: READ_ONLY_TOOLS.has(name),
|
|
283
|
+
openWorldHint: true,
|
|
284
|
+
},
|
|
235
285
|
})),
|
|
236
286
|
}));
|
|
237
287
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
@@ -246,8 +296,15 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
246
296
|
try {
|
|
247
297
|
const args = tool.schema.parse(req.params.arguments ?? {});
|
|
248
298
|
const result = await tool.handler(args);
|
|
299
|
+
// structuredContent espelha o retorno pra consumidor programático (spec
|
|
300
|
+
// 2025-06-18). Retrocompatível: client velho lê só o content textual. Como
|
|
301
|
+
// structuredContent precisa ser objeto, embrulho array/escalar em {result}.
|
|
302
|
+
const structuredContent = result && typeof result === "object" && !Array.isArray(result)
|
|
303
|
+
? result
|
|
304
|
+
: { result };
|
|
249
305
|
return {
|
|
250
306
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
307
|
+
structuredContent,
|
|
251
308
|
};
|
|
252
309
|
}
|
|
253
310
|
catch (e) {
|
|
@@ -259,4 +316,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
259
316
|
});
|
|
260
317
|
const transport = new StdioServerTransport();
|
|
261
318
|
await server.connect(transport);
|
|
262
|
-
console.error(`mcp-sapiens
|
|
319
|
+
console.error(`mcp-sapiens v${MCP_VERSION} rodando via stdio (${Object.keys(TOOLS).length} tools)`);
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Rejeita alvos locais óbvios (localhost / IP privado / link-local literal).
|
|
4
|
+
* NÃO é uma allowlist de host: só barra o que nunca é referência legítima.
|
|
5
|
+
*/
|
|
6
|
+
function isPrivateHost(hostname) {
|
|
7
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
8
|
+
if (host === "localhost" || host.endsWith(".localhost"))
|
|
9
|
+
return true;
|
|
10
|
+
if (host === "::1" || host.startsWith("fc") || host.startsWith("fd"))
|
|
11
|
+
return true; // IPv6 loopback/ULA
|
|
12
|
+
if (host === "0.0.0.0")
|
|
13
|
+
return true;
|
|
14
|
+
if (/^127\./.test(host))
|
|
15
|
+
return true; // loopback
|
|
16
|
+
if (/^10\./.test(host))
|
|
17
|
+
return true; // privado
|
|
18
|
+
if (/^192\.168\./.test(host))
|
|
19
|
+
return true; // privado
|
|
20
|
+
if (/^169\.254\./.test(host))
|
|
21
|
+
return true; // link-local
|
|
22
|
+
if (/^172\.(1[6-9]|2\d|3[01])\./.test(host))
|
|
23
|
+
return true; // privado 172.16-31
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Zod pra um campo de URL de referência pública (imagem/vídeo que o backend vai
|
|
28
|
+
* buscar). Valida que é http(s) parseável e não aponta pra localhost/IP privado.
|
|
29
|
+
*
|
|
30
|
+
* É defesa-em-profundidade NO CLIENTE: rejeita lixo cedo com erro claro. A
|
|
31
|
+
* allowlist de HOST autoritativa (Bunny/Convex/Wikimedia/YouTube...) vive
|
|
32
|
+
* server-side no Convex e NÃO é duplicada aqui de propósito — se fosse, liberar
|
|
33
|
+
* um CDN novo no backend faria toda versão já publicada do pacote passar a
|
|
34
|
+
* recusar URL válida até republicar e reiniciar cada client (bug de skew). Aqui
|
|
35
|
+
* só cai o que é universalmente inválido.
|
|
36
|
+
*/
|
|
37
|
+
export function httpUrl() {
|
|
38
|
+
return z
|
|
39
|
+
.string()
|
|
40
|
+
.url()
|
|
41
|
+
.refine((u) => {
|
|
42
|
+
try {
|
|
43
|
+
const { protocol, hostname } = new URL(u);
|
|
44
|
+
if (protocol !== "http:" && protocol !== "https:")
|
|
45
|
+
return false;
|
|
46
|
+
return !isPrivateHost(hostname);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}, "URL deve ser http(s) pública (sem localhost nem IP privado).");
|
|
52
|
+
}
|
package/dist/tools/article.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { httpUrl } from "../schema.js";
|
|
2
3
|
import { convexAction, convexMutation, convexQuery, getSessionToken, } from "../convexClient.js";
|
|
3
4
|
/**
|
|
4
5
|
* CRUD direto de artigos do blog Sapiens via session token. Substitui o
|
|
@@ -38,9 +39,8 @@ export const articleSchema = z.object({
|
|
|
38
39
|
tldr: z.string().optional(),
|
|
39
40
|
content: z.string().optional(),
|
|
40
41
|
tags: z.array(z.string()).optional(),
|
|
41
|
-
thumbnailUrl:
|
|
42
|
-
ogImageUrl:
|
|
43
|
-
.string()
|
|
42
|
+
thumbnailUrl: httpUrl().optional(),
|
|
43
|
+
ogImageUrl: httpUrl()
|
|
44
44
|
.optional()
|
|
45
45
|
.describe("update: JPEG scraper-safe pro preview social (og:image/twitter:image). Ao recapear um artigo, troque junto com thumbnailUrl (webp), senão o card de compartilhamento do WhatsApp/LinkedIn fica com a imagem antiga."),
|
|
46
46
|
bodyImages: z
|
package/dist/tools/character.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { httpUrl } from "../schema.js";
|
|
2
3
|
import { convexQuery, convexMutation, getSessionToken } from "../convexClient.js";
|
|
3
4
|
/**
|
|
4
5
|
* sapiens_character — personagens (character sheets) do Sapiens.
|
|
@@ -60,8 +61,7 @@ export const characterSchema = z.object({
|
|
|
60
61
|
.string()
|
|
61
62
|
.optional()
|
|
62
63
|
.describe("Pra create/set_card: a 'alma' do personagem (personalidade, jeito de falar, contexto). Usado no chat e como guia de geração."),
|
|
63
|
-
imageUrl:
|
|
64
|
-
.string()
|
|
64
|
+
imageUrl: httpUrl()
|
|
65
65
|
.optional()
|
|
66
66
|
.describe("Pra add_image: URL pública da imagem (Bunny CDN / Convex storage). Use a `url` que sapiens_image/sapiens_gallery devolvem."),
|
|
67
67
|
sourceImageId: z
|
package/dist/tools/forum.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { httpUrl } from "../schema.js";
|
|
2
3
|
import { convexQuery, convexMutation, getSessionToken, } from "../convexClient.js";
|
|
3
4
|
/**
|
|
4
5
|
* Fórum de Ressonância — o campo onde a Sintonia ressoa, via MCP.
|
|
@@ -66,8 +67,7 @@ export const forumSchema = z.object({
|
|
|
66
67
|
.string()
|
|
67
68
|
.optional()
|
|
68
69
|
.describe("Pra post: anexa uma FAIXA pronta sua (o trackId do sapiens_musicator) como card de música tocável na tese. O servidor valida posse + status."),
|
|
69
|
-
mediaUrl:
|
|
70
|
-
.string()
|
|
70
|
+
mediaUrl: httpUrl()
|
|
71
71
|
.optional()
|
|
72
72
|
.describe("Pra post: anexa vídeo/imagem por URL como card. Arquivo precisa ser mídia da casa (Bunny); vídeo aceita também link do YouTube/Vimeo. Use junto com mediaKind. (Música é via mediaTrackId, não aqui.)"),
|
|
73
73
|
mediaKind: z
|
|
@@ -78,8 +78,7 @@ export const forumSchema = z.object({
|
|
|
78
78
|
.string()
|
|
79
79
|
.optional()
|
|
80
80
|
.describe("Pra post com mídia: título da peça (faixa/vídeo)."),
|
|
81
|
-
mediaCoverUrl:
|
|
82
|
-
.string()
|
|
81
|
+
mediaCoverUrl: httpUrl()
|
|
83
82
|
.optional()
|
|
84
83
|
.describe("Pra post com mediaUrl video: poster/capa (mídia da casa/Bunny)."),
|
|
85
84
|
mediaAlt: z
|
package/dist/tools/image.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { httpUrl } from "../schema.js";
|
|
2
3
|
import { convexAction, getSessionToken } from "../convexClient.js";
|
|
3
4
|
// IDs canônicos do catálogo (apps/sapiens/convex/shared/imageModels.ts).
|
|
4
5
|
// IDs fora dessa lista caem no fallback e o pricing vira 999. Sempre usar os
|
|
@@ -48,7 +49,7 @@ export const imageSchema = z.object({
|
|
|
48
49
|
.describe("Default 'none'. IDs de estilo no convex/shared/imageStyles.ts."),
|
|
49
50
|
negativePrompt: z.string().optional(),
|
|
50
51
|
referenceImageUrls: z
|
|
51
|
-
.array(
|
|
52
|
+
.array(httpUrl())
|
|
52
53
|
.optional()
|
|
53
54
|
.describe("Até 4 URLs públicas de referência pra combinar numa geração só (character/style lock), igual ao modal 'Selecionar Referência' do gerador web. Fontes: sua galeria (sapiens_gallery, campo url), o Acervo, e personagens públicos (sapiens_character action=list_public → mainImageUrl/imageUrls). Restrito a hosts do Sapiens (Bunny CDN / Convex) + Wikimedia. Requer model com refs: nano-banana-2, gpt-image-2-* ou grok-2-image*. Soma com sourceImageIds (teto total de 4)."),
|
|
54
55
|
sourceImageIds: z
|
|
@@ -98,8 +99,7 @@ export const imageSchema = z.object({
|
|
|
98
99
|
.optional()
|
|
99
100
|
.describe("Pra action=compose: base64 da tela. Alt: screenImageUrl."),
|
|
100
101
|
screenImageMimeType: z.string().optional(),
|
|
101
|
-
screenImageUrl:
|
|
102
|
-
.string()
|
|
102
|
+
screenImageUrl: httpUrl()
|
|
103
103
|
.optional()
|
|
104
104
|
.describe("Pra action=compose: URL da tela (Bunny CDN). Convex baixa server-side."),
|
|
105
105
|
instruction: z
|
package/dist/tools/meta.js
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import path from "node:path";
|
|
5
2
|
import { convexQuery, convexMutation, convexAction, getSessionToken, saveSessionToken, clearSessionToken, describeConvexError, } from "../convexClient.js";
|
|
6
|
-
|
|
3
|
+
import { getMcpVersion } from "../version.js";
|
|
7
4
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* serve pra flagrar client preso em cache antigo do npx. dist/tools/meta.js ->
|
|
11
|
-
* ../../package.json (raiz do pacote); em dev (src/tools) o caminho bate igual.
|
|
5
|
+
* Versão do MCP realmente rodando (fonte única em ../version.js, lê o
|
|
6
|
+
* package.json em runtime). Serve pra flagrar client preso em cache antigo do npx.
|
|
12
7
|
*/
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const pkgPath = path.resolve(__dirname, "..", "..", "package.json");
|
|
16
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
17
|
-
return typeof pkg?.version === "string" ? pkg.version : "unknown";
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return "unknown";
|
|
21
|
-
}
|
|
22
|
-
}
|
|
8
|
+
const readMcpVersion = getMcpVersion;
|
|
23
9
|
export const metaSchema = z.object({
|
|
24
10
|
action: z.enum([
|
|
25
11
|
"start",
|
package/dist/tools/video.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { convexAction, convexQuery, convexMutation, getSessionToken } from "../convexClient.js";
|
|
3
|
+
import { httpUrl } from "../schema.js";
|
|
3
4
|
/**
|
|
4
5
|
* Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as
|
|
5
6
|
* Sinapses da conta do sessionToken).
|
|
@@ -55,8 +56,7 @@ const VIDEO_MODELS = [
|
|
|
55
56
|
export const videoSchema = z.object({
|
|
56
57
|
action: z.enum(["create", "generate", "demos", "showcase", "shadows", "shadows-list"]),
|
|
57
58
|
// --- action=shadows (deepshadows: extrai o mapa de profundidade de um vídeo) ---
|
|
58
|
-
videoUrl:
|
|
59
|
-
.string()
|
|
59
|
+
videoUrl: httpUrl()
|
|
60
60
|
.optional()
|
|
61
61
|
.describe("action=shadows: URL pública (http/https) do vídeo-fonte. O servidor extrai a SOMBRA (depth) e guarda no Acervo (Corpo). ADMIN, 200 Sinapses/segundo (refund na falha)."),
|
|
62
62
|
title: z
|
|
@@ -149,16 +149,14 @@ export const videoSchema = z.object({
|
|
|
149
149
|
.string()
|
|
150
150
|
.optional()
|
|
151
151
|
.describe("Frame inicial (i2v): generatedImages:_id da SUA galeria. Vira reference role 'start'."),
|
|
152
|
-
startImageUrl:
|
|
153
|
-
.string()
|
|
152
|
+
startImageUrl: httpUrl()
|
|
154
153
|
.optional()
|
|
155
154
|
.describe("Frame inicial (i2v): url pública (Bunny/Convex/Wikimedia) de galeria/Acervo/personagem. Vira reference role 'start'."),
|
|
156
155
|
endImageId: z
|
|
157
156
|
.string()
|
|
158
157
|
.optional()
|
|
159
158
|
.describe("Frame FINAL: generatedImages:_id da SUA galeria. Vira reference role 'end' (suporte varia por modelo)."),
|
|
160
|
-
endImageUrl:
|
|
161
|
-
.string()
|
|
159
|
+
endImageUrl: httpUrl()
|
|
162
160
|
.optional()
|
|
163
161
|
.describe("Frame FINAL: url pública de galeria/Acervo/personagem. Vira reference role 'end' (suporte varia por modelo)."),
|
|
164
162
|
});
|
package/dist/tools/write.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { httpUrl } from "../schema.js";
|
|
2
3
|
import { convexQuery, convexMutation, convexAction, getSessionToken, } from "../convexClient.js";
|
|
3
4
|
/**
|
|
4
5
|
* sapiens_write — artigos self-serve do PRÓPRIO usuário (qualquer conta logada).
|
|
@@ -56,8 +57,7 @@ export const writeSchema = z.object({
|
|
|
56
57
|
.string()
|
|
57
58
|
.optional()
|
|
58
59
|
.describe("Capa PRONTA (opcional, só generate): id de uma imagem da TUA galeria (generatedImages:_id, ache via sapiens_gallery/sapiens_reference) que vira a capa do artigo em vez da capa-cortesia gerada do zero. Use quando você JÁ gerou a imagem (sapiens_image) e o artigo é sobre ela — assim a peça não nasce sem capa se a cortesia falhar."),
|
|
59
|
-
coverImageUrl:
|
|
60
|
-
.string()
|
|
60
|
+
coverImageUrl: httpUrl()
|
|
61
61
|
.optional()
|
|
62
62
|
.describe("Alternativa a coverImageId: URL de imagem do Sapiens (Bunny CDN / Convex) pra usar como capa pronta. Host fora da allowlist é recusado."),
|
|
63
63
|
// --- list ---
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
/**
|
|
6
|
+
* Fonte ÚNICA da versão do MCP, lida do package.json do próprio binário em
|
|
7
|
+
* runtime. Reflete o que está REALMENTE rodando (não um literal cravado no
|
|
8
|
+
* código nem valor server-side), então flagra client preso em cache antigo do
|
|
9
|
+
* npx. Tanto `dist/version.js` quanto `src/version.ts` ficam 1 nível acima do
|
|
10
|
+
* package.json (../package.json), então o caminho bate em build E em dev (tsx).
|
|
11
|
+
*
|
|
12
|
+
* Antes disto a versão vivia em 3 literais independentes (package.json + 2 no
|
|
13
|
+
* index.ts) sincronizados por um regex no release.sh — um bump off-path
|
|
14
|
+
* dessincronizava em silêncio. Agora package.json é a única fonte.
|
|
15
|
+
*/
|
|
16
|
+
export function getMcpVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const pkgPath = path.resolve(__dirname, "..", "package.json");
|
|
19
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
20
|
+
return typeof pkg?.version === "string" ? pkg.version : "unknown";
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export const MCP_VERSION = getMcpVersion();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapiens-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.29.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,8 +28,18 @@
|
|
|
28
28
|
"build": "tsc",
|
|
29
29
|
"dev": "tsx src/index.ts",
|
|
30
30
|
"start": "node dist/index.js",
|
|
31
|
+
"pretest": "npm run build",
|
|
32
|
+
"test": "node --test test/server.test.mjs",
|
|
31
33
|
"prepublishOnly": "npm run build"
|
|
32
34
|
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/inhabitants/sapiensinteticos.git",
|
|
38
|
+
"directory": "tools/mcp-sapiens"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/inhabitants/sapiensinteticos/issues"
|
|
42
|
+
},
|
|
33
43
|
"dependencies": {
|
|
34
44
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
35
45
|
"convex": "^1.41.0",
|