sapiens-mcp 1.0.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 +45 -0
- package/dist/convexClient.js +167 -0
- package/dist/index.js +137 -0
- package/dist/tools/article.js +155 -0
- package/dist/tools/community.js +74 -0
- package/dist/tools/gallery.js +57 -0
- package/dist/tools/helen.js +120 -0
- package/dist/tools/image.js +145 -0
- package/dist/tools/meta.js +241 -0
- package/dist/tools/musicator.js +77 -0
- package/dist/tools/persona.js +87 -0
- package/dist/tools/pipeline.js +294 -0
- package/dist/tools/quotePop.js +175 -0
- package/dist/tools/repertorio.js +217 -0
- package/dist/tools/search.js +60 -0
- package/dist/tools/shorts.js +79 -0
- package/dist/tools/studios.js +169 -0
- package/dist/tools/video.js +55 -0
- package/dist/tools/write.js +132 -0
- package/package.json +44 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { convexQuery, getSessionToken } from "../convexClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* Busca por substring nos artigos do blog Sapiens (v1.2).
|
|
5
|
+
*
|
|
6
|
+
* Não é full-text engine — substring case-insensitive em
|
|
7
|
+
* title/excerpt/tldr/slug/tags. Suficiente pro fluxo "qual era o slug
|
|
8
|
+
* daquele artigo que falei sobre Stallman e modelos abertos?".
|
|
9
|
+
*
|
|
10
|
+
* Filtros opcionais ajudam a estreitar:
|
|
11
|
+
* - column: "sapiens" / "repertorio" / outra
|
|
12
|
+
* - format: "short" / "essay" / "pop-article" / etc
|
|
13
|
+
* - status: "draft" / "published" / "archived"
|
|
14
|
+
* - tag: tag específica (string match exato em tags[])
|
|
15
|
+
*/
|
|
16
|
+
export const searchSchema = z.object({
|
|
17
|
+
query: z.string().describe("Substring pra buscar (min 2 chars)."),
|
|
18
|
+
column: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Default: todos. 'sapiens' (Coluna) ou 'repertorio' (Coluna Repertório) são os mais usados."),
|
|
22
|
+
format: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Ex 'short', 'essay', 'pop-article', 'long'."),
|
|
26
|
+
status: z
|
|
27
|
+
.enum(["draft", "published", "archived"])
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Default: todos. Use 'published' pra evitar drafts no resultado."),
|
|
30
|
+
tag: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Match exato em tags[] do article. Ex: 'foss', 'estoico', 'sapiens-column'."),
|
|
34
|
+
limit: z
|
|
35
|
+
.number()
|
|
36
|
+
.int()
|
|
37
|
+
.positive()
|
|
38
|
+
.max(100)
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Default 30, max 100."),
|
|
41
|
+
});
|
|
42
|
+
export async function search(args) {
|
|
43
|
+
const sessionToken = getSessionToken();
|
|
44
|
+
if (!args.query || args.query.trim().length < 2) {
|
|
45
|
+
return {
|
|
46
|
+
count: 0,
|
|
47
|
+
results: [],
|
|
48
|
+
note: "query muito curta — passe pelo menos 2 chars",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return await convexQuery("mcpExtras:mcpSearchArticles", {
|
|
52
|
+
sessionToken,
|
|
53
|
+
query: args.query,
|
|
54
|
+
column: args.column,
|
|
55
|
+
format: args.format,
|
|
56
|
+
status: args.status,
|
|
57
|
+
tag: args.tag,
|
|
58
|
+
limit: args.limit,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { convexAction, getSessionToken } from "../convexClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sapiens Shorts — vídeo vertical 9:16 via VEO (v1.5).
|
|
5
|
+
*
|
|
6
|
+
* Sub-action:
|
|
7
|
+
* - render: dispara render com brief structured + imageId persona pré-existente.
|
|
8
|
+
*
|
|
9
|
+
* Pré-requisitos pro caller:
|
|
10
|
+
* 1. Gerar imagem persona via sapiens_image (ou escolher do gallery via sapiens_gallery)
|
|
11
|
+
* 2. Ter créditos suficientes (custo varia por modelo)
|
|
12
|
+
* 3. Brief montado segundo schema (product/hook/shots/vibe)
|
|
13
|
+
*
|
|
14
|
+
* Estilos:
|
|
15
|
+
* - ugc: real-life talking-head (mais comum)
|
|
16
|
+
* - unboxing: close em produto físico
|
|
17
|
+
* - app-demo: persona + tela
|
|
18
|
+
* - reflexao: atmosfera mais lenta, contemplativa
|
|
19
|
+
*
|
|
20
|
+
* Retorna `{ success: true, url }` ou `{ success: false, error, isBlocked? }`.
|
|
21
|
+
* O url é da Veo response e tem expiração curta — caller deve baixar logo.
|
|
22
|
+
*/
|
|
23
|
+
export const shortsSchema = z.object({
|
|
24
|
+
action: z.enum(["render"]),
|
|
25
|
+
imageId: z.string().describe("generatedImages:_id da persona base. Use sapiens_gallery action=list pra descobrir."),
|
|
26
|
+
styleId: z.enum(["ugc", "unboxing", "app-demo", "reflexao"]),
|
|
27
|
+
brief: z.object({
|
|
28
|
+
product: z.object({
|
|
29
|
+
name: z.string(),
|
|
30
|
+
type: z.enum(["app", "physical", "saas"]),
|
|
31
|
+
uvps: z.array(z.string()).describe("Unique Value Propositions, 2-4 bullets curtos"),
|
|
32
|
+
persona: z.string().describe("Descrição da persona-protagonista (idade/contexto/estilo)"),
|
|
33
|
+
}),
|
|
34
|
+
hook: z.object({
|
|
35
|
+
line: z.string().describe("Frase de abertura punchy, 5-10 palavras"),
|
|
36
|
+
emotion: z.string().describe("Emoção alvo (ex: 'curiosity', 'frustration', 'awe')"),
|
|
37
|
+
}),
|
|
38
|
+
shots: z
|
|
39
|
+
.array(z.object({
|
|
40
|
+
sec: z.number().describe("Duração em segundos (cap 8 por shot)"),
|
|
41
|
+
role: z.enum(["hook", "problem", "solution", "cta"]),
|
|
42
|
+
camera: z.enum(["close-up", "medium", "over-shoulder", "product-pov"]),
|
|
43
|
+
action: z.string().describe("Ação visual descrita em 1 frase"),
|
|
44
|
+
emotion: z.string(),
|
|
45
|
+
voiceLine: z.string().optional().describe("Linha falada na cena (PT-BR)"),
|
|
46
|
+
propVisible: z.string().optional(),
|
|
47
|
+
}))
|
|
48
|
+
.min(2)
|
|
49
|
+
.max(6)
|
|
50
|
+
.describe("2-6 shots. Total 15-60s. Geralmente: hook → problem → solution → cta."),
|
|
51
|
+
vibe: z.object({
|
|
52
|
+
energy: z.enum(["calm", "high"]).describe("Energia geral do edit"),
|
|
53
|
+
language: z.enum(["pt-BR", "en"]).optional().describe("Default 'pt-BR'"),
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
references: z
|
|
57
|
+
.array(z.object({
|
|
58
|
+
mimeType: z.string(),
|
|
59
|
+
data: z.string(),
|
|
60
|
+
role: z.string().optional(),
|
|
61
|
+
}))
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("References opcionais (start/end frames) em base64. Se omitido, persona é o start frame default."),
|
|
64
|
+
});
|
|
65
|
+
export async function shorts(args) {
|
|
66
|
+
if (args.action === "render") {
|
|
67
|
+
const sessionToken = getSessionToken();
|
|
68
|
+
if (args.brief.shots.length === 0) {
|
|
69
|
+
throw new Error("brief.shots vazio — passe 2-6 shots descrevendo a sequência.");
|
|
70
|
+
}
|
|
71
|
+
return await convexAction("mcpExtrasActions:mcpShortsRender", {
|
|
72
|
+
sessionToken,
|
|
73
|
+
imageId: args.imageId,
|
|
74
|
+
styleId: args.styleId,
|
|
75
|
+
brief: args.brief,
|
|
76
|
+
references: args.references,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Catálogo dos estúdios/experimentos Sapiens (v1.3).
|
|
4
|
+
*
|
|
5
|
+
* Versão minimal: retorna info estática sobre os experimentos disponíveis no
|
|
6
|
+
* app. Cada entry tem URL do dashboard, descrição curta, status (estável /
|
|
7
|
+
* beta / experimental), e tags. Útil pro Claude rotear o user pro lugar
|
|
8
|
+
* certo quando ele pede coisa que não está coberta no plugin v1.x.
|
|
9
|
+
*
|
|
10
|
+
* v1.4 vai adicionar wrappers session-token-auth pros estúdios que justificam
|
|
11
|
+
* (Helen Voice TTS, Musicator letras, Sapiens Shorts render, Persona Atlas).
|
|
12
|
+
* Por hora skills usam Claude-side + dashboard URL.
|
|
13
|
+
*/
|
|
14
|
+
export const studiosSchema = z.object({
|
|
15
|
+
action: z.enum(["list", "get", "publishable_url"]),
|
|
16
|
+
studio: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Pra action=get: slug do estúdio (ex 'helen-voice', 'musicator', 'persona-atlas', 'sapiens-shorts')."),
|
|
20
|
+
publishableId: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Pra action=publishable_url: ID do publishable. Retorna URL canônica /articles/<slug>."),
|
|
24
|
+
slug: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Alternativa a publishableId — passe o slug direto."),
|
|
28
|
+
});
|
|
29
|
+
const APP = "https://sapiensinteticos.com";
|
|
30
|
+
const STUDIOS = {
|
|
31
|
+
"helen-voice": {
|
|
32
|
+
name: "Helen Voice (TTS)",
|
|
33
|
+
description: "Text-to-speech via ElevenLabs ou Google TTS (Gemini). BYOK suportado. Voice cloning a partir de samples (UI-only). v1.4: speak via MCP.",
|
|
34
|
+
url: `${APP}/dashboard/influencer-ia`,
|
|
35
|
+
status: "stable",
|
|
36
|
+
tags: ["audio", "tts", "elevenlabs", "google", "voice"],
|
|
37
|
+
convex: "helenVoiceActions",
|
|
38
|
+
mcpReady: true,
|
|
39
|
+
mcpNote: "Coberto via sapiens_helen (speak + list_presets). Voice cloning de samples continua UI-driven.",
|
|
40
|
+
},
|
|
41
|
+
musicator: {
|
|
42
|
+
name: "Musicator",
|
|
43
|
+
description: "Gerador de música com letra + estilo + variantes. LLM escreve letra (PT), synth musical (Lyria/ACE/Suno) renderiza. v1.4: lyrics via MCP.",
|
|
44
|
+
url: `${APP}/experimentos/musicator`,
|
|
45
|
+
status: "stable",
|
|
46
|
+
tags: ["audio", "music", "lyrics", "ace", "suno", "lyria"],
|
|
47
|
+
convex: "musicatorActions",
|
|
48
|
+
mcpReady: true,
|
|
49
|
+
mcpNote: "Coberto via sapiens_musicator (lyrics). Render do áudio continua UI-driven (precisa criar track + scheduler).",
|
|
50
|
+
},
|
|
51
|
+
"persona-atlas": {
|
|
52
|
+
name: "Persona Atlas",
|
|
53
|
+
description: "16 arquétipos MBTI ilustrados (full-bleed, Sapiens style). Admin-only. Gera ou regera por código MBTI. v1.4: generate via MCP.",
|
|
54
|
+
url: `${APP}/experimentos/personagem-atlas`,
|
|
55
|
+
status: "stable",
|
|
56
|
+
tags: ["persona", "mbti", "character", "atlas"],
|
|
57
|
+
convex: "personaArt",
|
|
58
|
+
mcpReady: true,
|
|
59
|
+
mcpNote: "Coberto via sapiens_persona (generate + list_codes).",
|
|
60
|
+
},
|
|
61
|
+
"sapiens-shorts": {
|
|
62
|
+
name: "Sapiens Shorts (UGC / Reflexão)",
|
|
63
|
+
description: "Gera shorts verticais 9:16 via VEO. Estilos: ugc, unboxing, app-demo, reflexao. Composição automática persona + screen. v1.5: render via MCP.",
|
|
64
|
+
url: `${APP}/imgen-sapiens-shorts`,
|
|
65
|
+
status: "stable",
|
|
66
|
+
tags: ["video", "shorts", "9:16", "veo", "ugc"],
|
|
67
|
+
convex: "sapiensShortsActions",
|
|
68
|
+
mcpReady: true,
|
|
69
|
+
mcpNote: "Coberto via sapiens_shorts (render com brief structured). Compose persona+screen via composeWithImageUrl continua UI-only (compose tem credit refund que mexe em scheduler).",
|
|
70
|
+
},
|
|
71
|
+
"sapiens-video": {
|
|
72
|
+
name: "Sapiens Video (longer-form)",
|
|
73
|
+
description: "Vídeo programático longer-form com prompt livre. v1.5: generate via MCP.",
|
|
74
|
+
url: `${APP}/imgen-sapiens-video`,
|
|
75
|
+
status: "beta",
|
|
76
|
+
tags: ["video", "longform", "voiceover", "veo"],
|
|
77
|
+
convex: "sapiensVideoActions",
|
|
78
|
+
mcpReady: true,
|
|
79
|
+
mcpNote: "Coberto via sapiens_video (generate). Aspect configurável, references opcionais (start/end frames).",
|
|
80
|
+
},
|
|
81
|
+
"cena-visual": {
|
|
82
|
+
name: "Sapiens Cena Visual",
|
|
83
|
+
description: "Still única pra abertura, ou hero shot, ou simbólica.",
|
|
84
|
+
url: `${APP}/imgen-sapiens-cena-visual`,
|
|
85
|
+
status: "stable",
|
|
86
|
+
tags: ["image", "still", "hero"],
|
|
87
|
+
convex: "sapiensCenaVisualActions",
|
|
88
|
+
mcpReady: false,
|
|
89
|
+
},
|
|
90
|
+
"text-post-builder": {
|
|
91
|
+
name: "Text Post Builder",
|
|
92
|
+
description: "Gerador de post de texto puro pra rede social (Instagram, LinkedIn, Twitter). Inclui geração de imagem opcional acoplada.",
|
|
93
|
+
url: `${APP}/experimentos/text-post-builder`,
|
|
94
|
+
status: "stable",
|
|
95
|
+
tags: ["text", "social", "post", "instagram", "linkedin"],
|
|
96
|
+
convex: "textPostGenerator",
|
|
97
|
+
mcpReady: true,
|
|
98
|
+
mcpNote: "Parcialmente coberto via sapiens_pipeline format=post_social (texto Claude-side) + sapiens_image.",
|
|
99
|
+
},
|
|
100
|
+
"carrosel-editorial": {
|
|
101
|
+
name: "Carrossel Editorial",
|
|
102
|
+
description: "Gerador de carrossel Instagram com templates fixos (role × variant A/B/C), 9 slides Sapiens. Render via Playwright.",
|
|
103
|
+
url: `${APP}/experimentos/carrosel-editorial`,
|
|
104
|
+
status: "stable",
|
|
105
|
+
tags: ["carrossel", "instagram", "editorial", "playwright"],
|
|
106
|
+
convex: "carouselStandalone",
|
|
107
|
+
mcpReady: true,
|
|
108
|
+
mcpNote: "Coberto via sapiens_pipeline format=carrossel_ig (texto+imagens). Render Playwright fica no dashboard.",
|
|
109
|
+
},
|
|
110
|
+
comunidade: {
|
|
111
|
+
name: "Comunidade Sapiens",
|
|
112
|
+
description: "Chat compartilhado entre assinantes/alumni. Posts com voz Sapiens.",
|
|
113
|
+
url: `${APP}/comunidade`,
|
|
114
|
+
status: "stable",
|
|
115
|
+
tags: ["chat", "comunidade"],
|
|
116
|
+
convex: "communityChat",
|
|
117
|
+
mcpReady: true,
|
|
118
|
+
mcpNote: "Coberto via sapiens_community.",
|
|
119
|
+
},
|
|
120
|
+
repertorio: {
|
|
121
|
+
name: "Repertório",
|
|
122
|
+
description: "Catálogo pessoal de filme/série/anime/jogo. Importa de TMDB/Trakt/AniList. Pode virar pop-articles (lente cultural).",
|
|
123
|
+
url: `${APP}/u/<username>/repertorio`,
|
|
124
|
+
status: "stable",
|
|
125
|
+
tags: ["acervo", "filme", "anime", "jogo", "pop"],
|
|
126
|
+
convex: "repertorio",
|
|
127
|
+
mcpReady: true,
|
|
128
|
+
mcpNote: "Coberto via sapiens_repertorio (list/search/get + mutations v1.1).",
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
export async function studios(args) {
|
|
132
|
+
if (args.action === "list") {
|
|
133
|
+
return {
|
|
134
|
+
count: Object.keys(STUDIOS).length,
|
|
135
|
+
studios: Object.entries(STUDIOS).map(([slug, s]) => ({
|
|
136
|
+
slug,
|
|
137
|
+
...s,
|
|
138
|
+
})),
|
|
139
|
+
note: "Estúdios com mcpReady=true são operáveis direto pelo plugin. mcpReady=false exige UI do dashboard (link no campo url).",
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (args.action === "get") {
|
|
143
|
+
if (!args.studio)
|
|
144
|
+
throw new Error("action=get exige 'studio' (slug).");
|
|
145
|
+
const s = STUDIOS[args.studio];
|
|
146
|
+
if (!s) {
|
|
147
|
+
const known = Object.keys(STUDIOS).join(", ");
|
|
148
|
+
throw new Error(`Estúdio '${args.studio}' desconhecido. Disponíveis: ${known}`);
|
|
149
|
+
}
|
|
150
|
+
return { slug: args.studio, ...s };
|
|
151
|
+
}
|
|
152
|
+
if (args.action === "publishable_url") {
|
|
153
|
+
const slug = args.slug;
|
|
154
|
+
const pid = args.publishableId;
|
|
155
|
+
if (!slug && !pid) {
|
|
156
|
+
throw new Error("publishable_url exige 'slug' OU 'publishableId'.");
|
|
157
|
+
}
|
|
158
|
+
// Pra simplicidade v1.3, o caller passa o slug (que ele já tem do retorno
|
|
159
|
+
// do finalize_production). publishableId fica reservado pra v1.4 quando
|
|
160
|
+
// tivermos lookup slug-by-id.
|
|
161
|
+
return {
|
|
162
|
+
url: slug ? `${APP}/articles/${slug}` : null,
|
|
163
|
+
admin: `${APP}/dashboard/admin/content`,
|
|
164
|
+
note: slug
|
|
165
|
+
? "URL pública. Pra ver versões anteriores, use sapiens_pipeline list_versions."
|
|
166
|
+
: "publishableId lookup ainda não suportado v1.3. Passe slug.",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { convexAction, getSessionToken } from "../convexClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sapiens Video — render longer-form via VEO (v1.5).
|
|
5
|
+
*
|
|
6
|
+
* Sub-action:
|
|
7
|
+
* - generate: dispara render com prompt livre + imageId.
|
|
8
|
+
*
|
|
9
|
+
* Diferente de sapiens_shorts:
|
|
10
|
+
* - sem brief structured (prompt livre, mais maleável)
|
|
11
|
+
* - aspect ratio configurável (vertical 9:16, horizontal 16:9, etc)
|
|
12
|
+
* - longer-form (até 8s por chamada, mas pode encadear)
|
|
13
|
+
*
|
|
14
|
+
* Modelos (definidos quando o user criou o row na UI):
|
|
15
|
+
* - sapiens-video-lite (2000 sinapses, mais barato)
|
|
16
|
+
* - sapiens-video-fast (5000 sinapses, qualidade média)
|
|
17
|
+
* - sapiens-video-quality (25000 sinapses, top quality VEO 3.1)
|
|
18
|
+
*
|
|
19
|
+
* Retorna `{ success: true, url }` ou `{ success: false, error, isBlocked? }`.
|
|
20
|
+
*/
|
|
21
|
+
export const videoSchema = z.object({
|
|
22
|
+
action: z.enum(["generate"]),
|
|
23
|
+
imageId: z
|
|
24
|
+
.string()
|
|
25
|
+
.describe("generatedImages:_id pré-existente. Use sapiens_gallery action=list pra descobrir. " +
|
|
26
|
+
"O row do imageId define o modelo (lite/fast/quality) e o custo."),
|
|
27
|
+
prompt: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Prompt livre da cena. Se omitido, usa default 'A cinematic and creative sequence.' Default do VEO é razoável mas vago."),
|
|
31
|
+
aspectRatio: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Default da provider. Comum: '16:9' (horizontal), '9:16' (vertical), '1:1' (quadrado)."),
|
|
35
|
+
references: z
|
|
36
|
+
.array(z.object({
|
|
37
|
+
mimeType: z.string(),
|
|
38
|
+
data: z.string(),
|
|
39
|
+
role: z.string().optional().describe("'start' ou 'end' — frame de início ou fim"),
|
|
40
|
+
}))
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("References opcionais em base64 (start/end frames). Pra travar começo OU fim do vídeo numa imagem específica."),
|
|
43
|
+
});
|
|
44
|
+
export async function video(args) {
|
|
45
|
+
if (args.action === "generate") {
|
|
46
|
+
const sessionToken = getSessionToken();
|
|
47
|
+
return await convexAction("mcpExtrasActions:mcpVideoGenerate", {
|
|
48
|
+
sessionToken,
|
|
49
|
+
imageId: args.imageId,
|
|
50
|
+
prompt: args.prompt,
|
|
51
|
+
aspectRatio: args.aspectRatio,
|
|
52
|
+
references: args.references,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { convexQuery, convexMutation, convexAction, getSessionToken, } from "../convexClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* sapiens_write — artigos self-serve do PRÓPRIO usuário (qualquer conta logada).
|
|
5
|
+
*
|
|
6
|
+
* Diferente de `sapiens_article` (CRUD do blog editorial, owner-only): aqui o
|
|
7
|
+
* usuário escreve no espaço pessoal dele (tabela user_articles, aparece em
|
|
8
|
+
* /u/<username>), gastando as Sinapses dele. Identidade vem do sessionToken.
|
|
9
|
+
*
|
|
10
|
+
* Sub-actions:
|
|
11
|
+
* - generate: gera 1 artigo na voz Sapiens a partir de um brief livre
|
|
12
|
+
* (ou reescrevendo um artigo publicado / um texto teu). Cobra 400 Sinapses,
|
|
13
|
+
* reembolsa se a geração falhar. Salva como rascunho no teu perfil.
|
|
14
|
+
* - list: lista os teus artigos (rascunhos + publicados).
|
|
15
|
+
* - get: lê 1 artigo teu por id (corpo completo) pra revisar/editar.
|
|
16
|
+
* - update: edita title/content/excerpt/tldr do teu artigo.
|
|
17
|
+
* - publish: publica no teu perfil (publish=true) ou volta pra rascunho (false).
|
|
18
|
+
*/
|
|
19
|
+
export const writeSchema = z.object({
|
|
20
|
+
action: z.enum(["generate", "list", "get", "update", "publish"]),
|
|
21
|
+
// --- generate ---
|
|
22
|
+
brief: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Briefing livre (1 parágrafo, até ~600 palavras). Caminho padrão de 'generate'. A IA expande na voz Sapiens. Custa 400 Sinapses."),
|
|
26
|
+
sourceKind: z
|
|
27
|
+
.enum(["brief", "published_article", "user_article"])
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Default 'brief'. 'published_article' reescreve um artigo do blog (passe publishedArticleId); 'user_article' reescreve um texto teu (passe sourceUserArticleId)."),
|
|
30
|
+
publishedArticleId: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("articles:_id (quando sourceKind='published_article')."),
|
|
34
|
+
sourceUserArticleId: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("user_articles:_id (quando sourceKind='user_article')."),
|
|
38
|
+
voiceStyle: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Preset de tom da voz (opcional)."),
|
|
42
|
+
customVoice: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Instrução de voz custom (opcional)."),
|
|
46
|
+
// --- list ---
|
|
47
|
+
limit: z
|
|
48
|
+
.number()
|
|
49
|
+
.int()
|
|
50
|
+
.positive()
|
|
51
|
+
.max(100)
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Default 50. Max 100. Só pra action=list."),
|
|
54
|
+
// --- get / update / publish ---
|
|
55
|
+
articleId: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("user_articles:_id (obrigatório pra get/update/publish)."),
|
|
59
|
+
// --- update ---
|
|
60
|
+
title: z.string().optional(),
|
|
61
|
+
content: z
|
|
62
|
+
.string()
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Markdown completo do corpo (substitui o conteúdo)."),
|
|
65
|
+
excerpt: z.string().optional(),
|
|
66
|
+
tldr: z.string().optional(),
|
|
67
|
+
// --- publish ---
|
|
68
|
+
publish: z
|
|
69
|
+
.boolean()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("action=publish: true publica no teu perfil (/u/<username>), false volta pra rascunho. Default true."),
|
|
72
|
+
});
|
|
73
|
+
export async function write(args) {
|
|
74
|
+
const sessionToken = getSessionToken();
|
|
75
|
+
if (args.action === "generate") {
|
|
76
|
+
const sourceKind = args.sourceKind ?? "brief";
|
|
77
|
+
if (sourceKind === "brief" &&
|
|
78
|
+
(!args.brief || args.brief.trim().length < 20)) {
|
|
79
|
+
throw new Error("Pra gerar a partir de brief, escreve pelo menos 1 parágrafo (>=20 chars).");
|
|
80
|
+
}
|
|
81
|
+
if (sourceKind === "published_article" && !args.publishedArticleId) {
|
|
82
|
+
throw new Error("sourceKind='published_article' exige publishedArticleId.");
|
|
83
|
+
}
|
|
84
|
+
if (sourceKind === "user_article" && !args.sourceUserArticleId) {
|
|
85
|
+
throw new Error("sourceKind='user_article' exige sourceUserArticleId.");
|
|
86
|
+
}
|
|
87
|
+
return await convexAction("userArticlesActions:mcpUserGenerateArticle", {
|
|
88
|
+
sessionToken,
|
|
89
|
+
sourceKind,
|
|
90
|
+
brief: args.brief,
|
|
91
|
+
publishedArticleId: args.publishedArticleId,
|
|
92
|
+
sourceUserArticleId: args.sourceUserArticleId,
|
|
93
|
+
voiceStyle: args.voiceStyle,
|
|
94
|
+
customVoice: args.customVoice,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (args.action === "list") {
|
|
98
|
+
return await convexQuery("mcpExtras:mcpUserListArticles", {
|
|
99
|
+
sessionToken,
|
|
100
|
+
limit: args.limit ?? 50,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (args.action === "get") {
|
|
104
|
+
if (!args.articleId)
|
|
105
|
+
throw new Error("action=get exige articleId.");
|
|
106
|
+
return await convexQuery("mcpExtras:mcpUserGetArticle", {
|
|
107
|
+
sessionToken,
|
|
108
|
+
articleId: args.articleId,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (args.action === "update") {
|
|
112
|
+
if (!args.articleId)
|
|
113
|
+
throw new Error("action=update exige articleId.");
|
|
114
|
+
return await convexMutation("mcpExtras:mcpUserUpdateArticle", {
|
|
115
|
+
sessionToken,
|
|
116
|
+
articleId: args.articleId,
|
|
117
|
+
title: args.title,
|
|
118
|
+
content: args.content,
|
|
119
|
+
excerpt: args.excerpt,
|
|
120
|
+
tldr: args.tldr,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (args.action === "publish") {
|
|
124
|
+
if (!args.articleId)
|
|
125
|
+
throw new Error("action=publish exige articleId.");
|
|
126
|
+
return await convexMutation("mcpExtras:mcpUserPublishArticle", {
|
|
127
|
+
sessionToken,
|
|
128
|
+
articleId: args.articleId,
|
|
129
|
+
publish: args.publish ?? true,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sapiens-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sapiens-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://sapiensinteticos.com/conectar-claude",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"sapiens",
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"ai",
|
|
24
|
+
"image-generation"
|
|
25
|
+
],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"dev": "tsx src/index.ts",
|
|
30
|
+
"start": "node dist/index.js",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
35
|
+
"convex": "^1.39.1",
|
|
36
|
+
"zod": "^3.23.8",
|
|
37
|
+
"zod-to-json-schema": "^3.23.5"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.10.0",
|
|
41
|
+
"tsx": "^4.19.2",
|
|
42
|
+
"typescript": "^5.7.2"
|
|
43
|
+
}
|
|
44
|
+
}
|