sapiens-mcp 1.11.1 → 1.13.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Borderless (Sapiens Sintéticos)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { video, videoSchema } from "./tools/video.js";
21
21
  import { write, writeSchema } from "./tools/write.js";
22
22
  import { stockAudio, stockAudioSchema } from "./tools/stockAudio.js";
23
23
  import { brand, brandSchema } from "./tools/brand.js";
24
+ import { character, characterSchema } from "./tools/character.js";
24
25
  import { describeConvexError } from "./convexClient.js";
25
26
  const TOOLS = {
26
27
  sapiens_pipeline: {
@@ -44,7 +45,7 @@ const TOOLS = {
44
45
  handler: repertorio,
45
46
  },
46
47
  sapiens_gallery: {
47
- description: "Browse das imagens geradas pelo user (nanoBanana). Sub-actions: list (últimas N imagens, com prompt/model/url), get (1 imagem com metadados, opcionalmente base64). Use pra reusar imagem como referência (passe o imageId em sapiens_image mode=edit ou mode=variation), ou pra mostrar pro user o que ele já tem.",
48
+ description: "Browse e publicação das imagens geradas pelo user (nanoBanana). Sub-actions: list (últimas N imagens, com prompt/model/url + isPublic), get (1 imagem com metadados, opcionalmente base64), publish (torna a PRÓPRIA imagem pública: entra na galeria pública + feed Pinterest, e ganha página indexável /imagem/<id> se o modelo não for degen — devolve publicPageUrl), unpublish (volta a privada). Use list/get pra reusar imagem como referência (passe o imageId em sapiens_image mode=edit ou mode=variation) ou pra mostrar pro user o que ele já tem; publish quando o user quer divulgar a imagem dele.",
48
49
  schema: gallerySchema,
49
50
  handler: gallery,
50
51
  },
@@ -113,8 +114,13 @@ const TOOLS = {
113
114
  schema: brandSchema,
114
115
  handler: brand,
115
116
  },
117
+ sapiens_character: {
118
+ description: "Personagens (character sheets) do Sapiens — a tabela `influencers`: personagem reutilizável com imagens (pra character-lock em geração) + alma (systemPrompt), tudo amarrado à conta do dono do token (sem admin). Sub-actions: list_public (catálogo global de personagens públicos do Explorar; cada um traz mainImageUrl/imageUrls usáveis direto como referenceImageUrls em sapiens_image; sem custo, sem login), get (detalhe de 1 por characterId — público+ativo qualquer um vê, draft/privado só o dono; systemPrompt só volta pro dono), list_mine (os personagens do próprio user, inclui drafts/privados), create (cria rascunho na conta: name + gender + opcional title/systemPrompt), add_image (adiciona imagem ao próprio personagem via imageUrl público OU sourceImageId da galeria; 1ª vira principal), set_card (edita alma/título/nome do próprio), activate (publica, sai de draft, exige ≥1 imagem), set_visibility (isPublic true=Explorar+slug / false=privado). Fluxo de criação: create → add_image (1+) → set_card (opcional) → activate → set_visibility isPublic=true. Pra usar um personagem público como referência numa geração, pegue mainImageUrl em list_public/get e passe em sapiens_image referenceImageUrls.",
119
+ schema: characterSchema,
120
+ handler: character,
121
+ },
116
122
  };
117
- const server = new Server({ name: "mcp-sapiens", version: "1.11.0" }, { capabilities: { tools: {} } });
123
+ const server = new Server({ name: "mcp-sapiens", version: "1.13.0" }, { capabilities: { tools: {} } });
118
124
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
119
125
  tools: Object.entries(TOOLS).map(([name, t]) => ({
120
126
  name,
@@ -147,4 +153,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
147
153
  });
148
154
  const transport = new StdioServerTransport();
149
155
  await server.connect(transport);
150
- console.error("mcp-sapiens v1.11.0 rodando via stdio (18 tools)");
156
+ console.error("mcp-sapiens v1.13.0 rodando via stdio (19 tools)");
@@ -0,0 +1,191 @@
1
+ import { z } from "zod";
2
+ import { convexQuery, convexMutation, getSessionToken } from "../convexClient.js";
3
+ /**
4
+ * sapiens_character — personagens (character sheets) do Sapiens.
5
+ *
6
+ * "Character" aqui é a tabela `influencers`: personagem reutilizável com
7
+ * imagens (pra character-lock em geração) + alma (systemPrompt). Tudo amarrado
8
+ * à conta do dono do sessionToken. Sem nada de admin.
9
+ *
10
+ * Sub-actions:
11
+ * - list_public: catálogo global de personagens públicos (Explorar). Cada um
12
+ * traz mainImageUrl/imageUrls usáveis como referência em
13
+ * sapiens_image (referenceImageUrls). Sem custo.
14
+ * - get: detalhe de 1 personagem por id. Público+ativo: qualquer um.
15
+ * Draft/privado: só o dono. systemPrompt só volta pro dono.
16
+ * - list_mine: os personagens do próprio user (inclui drafts/privados).
17
+ * - create: cria um personagem (rascunho) na conta. name + gender.
18
+ * - add_image: adiciona imagem ao próprio personagem (imageUrl direto ou
19
+ * sourceImageId da galeria). 1ª imagem vira a principal.
20
+ * - set_card: edita a alma (systemPrompt), título e/ou nome do próprio.
21
+ * - activate: publica (sai de draft). Exige ≥1 imagem.
22
+ * - set_visibility: público (entra no Explorar, ganha slug) ou privado.
23
+ *
24
+ * Fluxo típico de criação: create → add_image (1+) → set_card (opcional) →
25
+ * activate → set_visibility isPublic=true.
26
+ */
27
+ export const characterSchema = z.object({
28
+ action: z.enum([
29
+ "list_public",
30
+ "get",
31
+ "list_mine",
32
+ "create",
33
+ "add_image",
34
+ "set_card",
35
+ "activate",
36
+ "set_visibility",
37
+ ]),
38
+ characterId: z
39
+ .string()
40
+ .optional()
41
+ .describe("ID do personagem (influencers:_id). Obrigatório em get/add_image/set_card/activate/set_visibility. Descubra via list_public ou list_mine."),
42
+ limit: z
43
+ .number()
44
+ .int()
45
+ .optional()
46
+ .describe("Pra list_public: quantos retornar (default 40, max 100)."),
47
+ name: z
48
+ .string()
49
+ .optional()
50
+ .describe("Pra create (obrigatório) ou set_card (renomear): nome do personagem."),
51
+ gender: z
52
+ .string()
53
+ .optional()
54
+ .describe("Pra create: 'masculino' | 'feminino' | 'nao-binario'. Default 'nao-binario'."),
55
+ title: z
56
+ .string()
57
+ .optional()
58
+ .describe("Pra create/set_card: subtítulo curto (ex: 'A guia do Sapiens')."),
59
+ systemPrompt: z
60
+ .string()
61
+ .optional()
62
+ .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: z
64
+ .string()
65
+ .optional()
66
+ .describe("Pra add_image: URL pública da imagem (Bunny CDN / Convex storage). Use a `url` que sapiens_image/sapiens_gallery devolvem."),
67
+ sourceImageId: z
68
+ .string()
69
+ .optional()
70
+ .describe("Pra add_image: alternativa ao imageUrl — ID de imagem da SUA galeria (generatedImages:_id, via sapiens_gallery action=list). O backend resolve a url e confere que é sua."),
71
+ isMain: z
72
+ .boolean()
73
+ .optional()
74
+ .describe("Pra add_image: marca esta como a imagem principal (avatar). 1ª imagem já vira main sozinha."),
75
+ isPublic: z
76
+ .boolean()
77
+ .optional()
78
+ .describe("Pra set_visibility: true = público no Explorar (gera slug), false = privado."),
79
+ });
80
+ export async function character(args) {
81
+ // -------- list_public: catálogo global (sessionToken opcional, marca `mine`) --------
82
+ if (args.action === "list_public") {
83
+ let sessionToken;
84
+ try {
85
+ sessionToken = getSessionToken();
86
+ }
87
+ catch {
88
+ sessionToken = undefined; // catálogo público funciona sem login
89
+ }
90
+ return await convexQuery("influencers:mcpListPublicCharacters", {
91
+ sessionToken,
92
+ limit: args.limit,
93
+ });
94
+ }
95
+ // -------- get: detalhe de 1 personagem --------
96
+ if (args.action === "get") {
97
+ if (!args.characterId) {
98
+ throw new Error("action=get exige characterId (pegue via list_public ou list_mine).");
99
+ }
100
+ let sessionToken;
101
+ try {
102
+ sessionToken = getSessionToken();
103
+ }
104
+ catch {
105
+ sessionToken = undefined;
106
+ }
107
+ const res = await convexQuery("influencers:mcpGetCharacter", {
108
+ sessionToken,
109
+ characterId: args.characterId,
110
+ });
111
+ if (!res) {
112
+ throw new Error(`Personagem "${args.characterId}" não encontrado (ou é privado de outro user).`);
113
+ }
114
+ return res;
115
+ }
116
+ // -------- list_mine: personagens do próprio user (inclui drafts) --------
117
+ if (args.action === "list_mine") {
118
+ const sessionToken = getSessionToken();
119
+ return await convexQuery("influencers:mcpListMyCharacters", { sessionToken });
120
+ }
121
+ // -------- create: novo rascunho na conta --------
122
+ if (args.action === "create") {
123
+ if (!args.name || !args.name.trim()) {
124
+ throw new Error("action=create exige 'name'.");
125
+ }
126
+ const sessionToken = getSessionToken();
127
+ return await convexMutation("influencers:mcpCreateCharacter", {
128
+ sessionToken,
129
+ name: args.name,
130
+ gender: args.gender,
131
+ title: args.title,
132
+ systemPrompt: args.systemPrompt,
133
+ });
134
+ }
135
+ // -------- add_image: imagem no próprio personagem --------
136
+ if (args.action === "add_image") {
137
+ if (!args.characterId)
138
+ throw new Error("action=add_image exige characterId.");
139
+ if (!args.imageUrl && !args.sourceImageId) {
140
+ throw new Error("action=add_image exige 'imageUrl' ou 'sourceImageId'.");
141
+ }
142
+ const sessionToken = getSessionToken();
143
+ return await convexMutation("influencers:mcpAddCharacterImage", {
144
+ sessionToken,
145
+ characterId: args.characterId,
146
+ imageUrl: args.imageUrl,
147
+ sourceImageId: args.sourceImageId,
148
+ isMain: args.isMain,
149
+ });
150
+ }
151
+ // -------- set_card: alma/título/nome do próprio personagem --------
152
+ if (args.action === "set_card") {
153
+ if (!args.characterId)
154
+ throw new Error("action=set_card exige characterId.");
155
+ if (args.systemPrompt === undefined && args.title === undefined && args.name === undefined) {
156
+ throw new Error("action=set_card precisa de pelo menos um: systemPrompt, title ou name.");
157
+ }
158
+ const sessionToken = getSessionToken();
159
+ return await convexMutation("influencers:mcpSetCharacterCard", {
160
+ sessionToken,
161
+ characterId: args.characterId,
162
+ systemPrompt: args.systemPrompt,
163
+ title: args.title,
164
+ name: args.name,
165
+ });
166
+ }
167
+ // -------- activate: publica (sai de draft, exige ≥1 imagem) --------
168
+ if (args.action === "activate") {
169
+ if (!args.characterId)
170
+ throw new Error("action=activate exige characterId.");
171
+ const sessionToken = getSessionToken();
172
+ return await convexMutation("influencers:mcpActivateCharacter", {
173
+ sessionToken,
174
+ characterId: args.characterId,
175
+ });
176
+ }
177
+ // -------- set_visibility: público (Explorar) ou privado --------
178
+ if (args.action === "set_visibility") {
179
+ if (!args.characterId)
180
+ throw new Error("action=set_visibility exige characterId.");
181
+ if (args.isPublic === undefined) {
182
+ throw new Error("action=set_visibility exige isPublic (true=público, false=privado).");
183
+ }
184
+ const sessionToken = getSessionToken();
185
+ return await convexMutation("influencers:mcpSetCharacterVisibility", {
186
+ sessionToken,
187
+ characterId: args.characterId,
188
+ isPublic: args.isPublic,
189
+ });
190
+ }
191
+ }
@@ -8,9 +8,10 @@ import { convexAction, getSessionToken } from "../convexClient.js";
8
8
  * - Listar imagens recentes do user (pra reusar como referenceImage)
9
9
  * - Pegar bytes de uma imagem pra mostrar inline no Claude
10
10
  * - Descobrir imageId pra passar como sourceImageId em sapiens_image edit/variation
11
+ * - Publicar/despublicar a própria imagem (galeria pública + feed Pinterest)
11
12
  */
12
13
  export const gallerySchema = z.object({
13
- action: z.enum(["list", "get"]),
14
+ action: z.enum(["list", "get", "publish", "unpublish"]),
14
15
  limit: z
15
16
  .number()
16
17
  .int()
@@ -25,7 +26,7 @@ export const gallerySchema = z.object({
25
26
  imageId: z
26
27
  .string()
27
28
  .optional()
28
- .describe("generatedImages:_id (obrigatório pra action=get)"),
29
+ .describe("generatedImages:_id (obrigatório pra action=get/publish/unpublish)"),
29
30
  includeBase64: z
30
31
  .boolean()
31
32
  .optional()
@@ -54,4 +55,16 @@ export async function gallery(args) {
54
55
  includeBase64: args.includeBase64 ?? false,
55
56
  });
56
57
  }
58
+ // publish/unpublish: liga/desliga isPublic na própria imagem. Public = entra
59
+ // na galeria pública + feed Pinterest (e /imagem/<id> se não for degen).
60
+ if (args.action === "publish" || args.action === "unpublish") {
61
+ if (!args.imageId) {
62
+ throw new Error(`action=${args.action} exige imageId. Use action=list pra descobrir.`);
63
+ }
64
+ return await convexAction("desktopMcp:gallerySetPublic", {
65
+ sessionToken,
66
+ imageId: args.imageId,
67
+ isPublic: args.action === "publish",
68
+ });
69
+ }
57
70
  }
@@ -8,6 +8,9 @@ const MODELS = [
8
8
  "nano-banana-2", // gemini-3.1-flash-image-preview (V2, Flash 3.1) · 450 + adder · COM refs · DEFAULT
9
9
  "gpt-image-2-low", // Azure gpt-image-2 quality=low · 250
10
10
  "gpt-image-2-high", // Azure gpt-image-2 quality=high · 800
11
+ // xAI Grok Imagine. Moderação frouxa (+18), aceita refs (img2img) + aspect.
12
+ "grok-2-image", // grok-imagine-image · 450 + adder 2K · COM refs
13
+ "grok-2-image-quality", // grok-imagine-image-quality, mais fiel pra character lock · 900 + adder 2K · COM refs
11
14
  // Degen (uncensored, gate +18 na galeria). WaveSpeed = rápido (6-25s):
12
15
  "wavespeed-chroma", // Chroma uncensored fotorrealista · 600
13
16
  "wavespeed-flux2", // Flux.2 Klein 9B · 600
@@ -24,7 +27,7 @@ export const imageSchema = z.object({
24
27
  model: z
25
28
  .enum(MODELS)
26
29
  .optional()
27
- .describe("Default 'nano-banana-2' (Flash 3.1 com refs). 'nano-banana-max' (Pro 3) = qualidade alta. 'gpt-image-2-low/high' = Azure. DEGEN (uncensored, gate +18): 'wavespeed-chroma' (fotorrealista rápido), 'wavespeed-flux2' (Flux.2 Klein), 'wavespeed-flux-nsfw' (flux+LoRA NSFW) = WaveSpeed rápido; 'civitai-wai-illustrious'/'civitai-nova-anime-xl' (anime), 'civitai-pony-v6' (Pony V6 XL, base nº1) = Civitai sdcpp rápido."),
30
+ .describe("Default 'nano-banana-2' (Flash 3.1 com refs). 'nano-banana-max' (Pro 3) = qualidade alta. 'gpt-image-2-low/high' = Azure. 'grok-2-image'/'grok-2-image-quality' = xAI Grok Imagine (moderação frouxa +18, aceita refs e aspect; quality é mais fiel pra character lock). DEGEN (uncensored, gate +18): 'wavespeed-chroma' (fotorrealista rápido), 'wavespeed-flux2' (Flux.2 Klein), 'wavespeed-flux-nsfw' (flux+LoRA NSFW) = WaveSpeed rápido; 'civitai-wai-illustrious'/'civitai-nova-anime-xl' (anime), 'civitai-pony-v6' (Pony V6 XL, base nº1) = Civitai sdcpp rápido."),
28
31
  aspectRatio: z
29
32
  .enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"])
30
33
  .optional()
@@ -41,7 +44,7 @@ export const imageSchema = z.object({
41
44
  referenceImageUrls: z
42
45
  .array(z.string())
43
46
  .optional()
44
- .describe("URLs públicas (Convex storage / Bunny CDN / Wikimedia) usadas como reference images. Trava character/style entre múltiplas gerações. Requer model com supportsReferences=true (nano-banana-2 ou gpt-image-2-*)."),
47
+ .describe("URLs públicas (Convex storage / Bunny CDN / Wikimedia) usadas como reference images. Trava character/style entre múltiplas gerações. Requer model com supportsReferences=true (nano-banana-2, gpt-image-2-* ou grok-2-image*)."),
45
48
  brandSlug: z
46
49
  .string()
47
50
  .optional()
@@ -74,11 +74,11 @@ const FORMAT_GUIDE = {
74
74
  },
75
75
  },
76
76
  cena_visual: {
77
- description: "Cena visual única pra still ou abertura.",
77
+ description: "Vídeo curto programático via Hyperframes (movimento + texto entram por código).",
78
78
  payloadHint: {
79
- prompt: "string",
80
- composition: "string",
81
- imageUrl: "string opcional",
79
+ concept: "string (ideia da cena)",
80
+ scenes: [{ text: "string", motion: "string", durationMs: "number" }],
81
+ durationMs: "number total opcional",
82
82
  },
83
83
  },
84
84
  video_yt: {
@@ -89,20 +89,20 @@ const FORMAT_GUIDE = {
89
89
  scenes: [{ visual: "string", voiceover: "string", duration: "number" }],
90
90
  },
91
91
  },
92
- post_linkedin: {
93
- description: "Post LinkedIn (texto + opcional carrossel curto).",
94
- payloadHint: { text: "string", hashtags: ["string"] },
95
- },
96
- post_twitter: {
97
- description: "Post Twitter/X (single ou thread).",
92
+ mega_grafico: {
93
+ description: "Pôster blueprint denso multi-seção (estilo Pop Chart), texto fiel via gpt-image-2. Tem runner próprio: propose_mega_grafico_plan / run_mega_grafico_full.",
98
94
  payloadHint: {
99
- tweets: ["string (max 280 chars)"],
100
- threadStyle: "boolean",
95
+ title: "string",
96
+ sections: [{ heading: "string", items: ["string"] }],
97
+ withHelen: "boolean (1 painel com a Helen interagindo com o tema)",
101
98
  },
102
99
  },
103
- post_threads: {
104
- description: "Post Threads (parecido com Twitter mas mais longo).",
105
- payloadHint: { posts: ["string"] },
100
+ aula: {
101
+ description: "Aula em slides (deck navegável no site /aulas) destilada do source: framework nomeado + analogia.",
102
+ payloadHint: {
103
+ title: "string",
104
+ slides: [{ title: "string", body: "string", note: "string opcional" }],
105
+ },
106
106
  },
107
107
  };
108
108
  export async function meta(args) {
@@ -1,18 +1,24 @@
1
1
  import { z } from "zod";
2
2
  import { convexAction, convexMutation, convexQuery, getSessionToken, } from "../convexClient.js";
3
+ // Espelho canônico dos formatos do app (peças surface:'format' em PIECES,
4
+ // apps/sapiens/src/features/content-pipeline/registry.jsx). Mantido em sync por
5
+ // lint: `npm run audit:mcp-format-parity` falha o CI se divergir. Ao adicionar
6
+ // ou remover formato, mexa nos dois lados.
3
7
  const FORMATS = [
4
8
  "tirinha",
5
9
  "carrossel_ig",
6
10
  "post_social",
7
- "musica",
8
11
  "mega_grafico",
9
- "post_linkedin",
10
- "post_twitter",
11
- "post_threads",
12
12
  "shorts_yt",
13
13
  "cena_visual",
14
14
  "video_yt",
15
+ "musica",
16
+ "aula",
15
17
  ];
18
+ // Formatos legados: fora do FORMAT_GUIDE (não advertidos), mas ainda aceitos na
19
+ // validação pra editar productions antigas sem quebrar.
20
+ const LEGACY_FORMATS = ["post_linkedin", "post_twitter", "post_threads"];
21
+ const ACCEPTED_FORMATS = [...FORMATS, ...LEGACY_FORMATS];
16
22
  const STATUSES = ["draft", "ready", "finalized"];
17
23
  export const pipelineSchema = z.object({
18
24
  action: z.enum([
@@ -75,7 +81,7 @@ export const pipelineSchema = z.object({
75
81
  publishableId: z.string().optional(),
76
82
  articleId: z.string().optional(),
77
83
  // create / add
78
- format: z.enum(FORMATS).optional(),
84
+ format: z.enum(ACCEPTED_FORMATS).optional(),
79
85
  payload: z.any().optional().describe("Payload livre por formato"),
80
86
  status: z.enum(STATUSES).optional(),
81
87
  notes: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.11.1",
3
+ "version": "1.13.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": {
@@ -13,7 +13,7 @@
13
13
  "engines": {
14
14
  "node": ">=18"
15
15
  },
16
- "homepage": "https://sapiensinteticos.com/conectar-claude",
16
+ "homepage": "https://www.sapiensinteticos.com/conectar-claude",
17
17
  "keywords": [
18
18
  "mcp",
19
19
  "model-context-protocol",