jotae-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 ADDED
@@ -0,0 +1,101 @@
1
+ # jotae-mcp
2
+
3
+ MCP Server oficial do [Jotae](https://app.jotae.me) para Claude Desktop e Claude.ai.
4
+
5
+ Conecta o Claude ao Jotae para criar eventos, configurar automações de WhatsApp e e-mail, e ler métricas — tudo por texto ou voz.
6
+
7
+ ## O que o agente consegue fazer
8
+
9
+ - Criar e editar eventos ao vivo
10
+ - Configurar data, link do YouTube, pitch e CTA
11
+ - Aplicar timelines de automação completas a um evento
12
+ - Criar automações individuais segmentadas (participantes, no-show, compradores)
13
+ - Ler métricas: presença, pitch, cliques no CTA, taxa de conversão
14
+ - Ver integrações ativas (WhatsApp, e-mail, CRM)
15
+ - Acessar contatos e listas geradas por evento
16
+
17
+ ## Instalação
18
+
19
+ ### 1. Gere sua chave de API
20
+
21
+ Acesse **Configurações > Integrações > Claude AI (MCP)** no Jotae e gere uma chave `jotae_sk_...`.
22
+
23
+ ### 2. Configure o Claude Desktop
24
+
25
+ Edite `~/Library/Application Support/Claude/claude_desktop_config.json`:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "jotae": {
31
+ "command": "npx",
32
+ "args": ["jotae-mcp"],
33
+ "env": {
34
+ "JOTAE_API_KEY": "jotae_sk_SUA_CHAVE_AQUI"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ Reinicie o Claude Desktop. O ícone de ferramentas aparece nas conversas.
42
+
43
+ ### 3. (Opcional) URL customizada
44
+
45
+ Se você usa domínio próprio:
46
+
47
+ ```json
48
+ "env": {
49
+ "JOTAE_API_KEY": "jotae_sk_...",
50
+ "JOTAE_BASE_URL": "https://app.seudominio.com"
51
+ }
52
+ ```
53
+
54
+ ## Como usar
55
+
56
+ Abra o Claude Desktop e use os prompts prontos:
57
+
58
+ - **"Configurar novo evento"** — guia completo do zero
59
+ - **"Follow-up pós-evento"** — automações segmentadas pós-live
60
+
61
+ Ou chame diretamente:
62
+
63
+ > "Cria um evento chamado 'Masterclass de Vendas' para 15 de setembro às 20h com CTA para minha página de vendas"
64
+
65
+ > "Quais eventos tenho cadastrados e qual a taxa de conversão do último?"
66
+
67
+ > "Aplica a timeline 'Lançamento Padrão' no evento X"
68
+
69
+ ## Ferramentas disponíveis
70
+
71
+ | Tool | O que faz |
72
+ |---|---|
73
+ | `get_setup_status` | Diagnóstico completo da conta |
74
+ | `list_events` | Lista eventos |
75
+ | `get_event` | Detalhes de um evento |
76
+ | `get_event_stats` | Métricas de presença e conversão |
77
+ | `create_event` | Cria evento |
78
+ | `update_event` | Edita evento |
79
+ | `list_timelines` | Lista timelines com etapas |
80
+ | `apply_timeline` | Aplica timeline a um evento |
81
+ | `list_automations` | Lista automações de um evento |
82
+ | `create_automation` | Cria automação individual |
83
+ | `update_automation` | Edita/ativa/desativa automação |
84
+ | `delete_automation` | Remove automação |
85
+ | `list_templates` | Lista templates WhatsApp e e-mail |
86
+ | `list_contacts` | Lista contatos |
87
+ | `list_contact_lists` | Lista de segmentos pós-evento |
88
+ | `list_integrations` | Integrações ativas e capacidades |
89
+
90
+ ## Desenvolvimento local
91
+
92
+ ```bash
93
+ git clone https://github.com/jackemerick/jotae-mcp
94
+ cd jotae-mcp
95
+ npm install
96
+ JOTAE_API_KEY=jotae_sk_... npm run dev
97
+ ```
98
+
99
+ ## Licença
100
+
101
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const BASE_URL = process.env.JOTAE_BASE_URL ?? 'https://app.jotae.me';
8
+ const API_KEY = process.env.JOTAE_API_KEY ?? '';
9
+ if (!API_KEY) {
10
+ process.stderr.write('JOTAE_API_KEY não configurada. Defina a variável de ambiente.\n');
11
+ process.exit(1);
12
+ }
13
+ async function api(method, path, body) {
14
+ const res = await fetch(`${BASE_URL}/api/v1${path}`, {
15
+ method,
16
+ headers: {
17
+ 'Authorization': `Bearer ${API_KEY}`,
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ body: body ? JSON.stringify(body) : undefined,
21
+ });
22
+ const json = await res.json();
23
+ if (!res.ok)
24
+ throw new Error(json.error ?? `HTTP ${res.status}`);
25
+ return json;
26
+ }
27
+ // ── Tools ────────────────────────────────────────────────────────────────────
28
+ const TOOLS = [
29
+ // ── Diagnóstico ──
30
+ {
31
+ name: 'get_setup_status',
32
+ description: 'Retorna o estado geral da conta: eventos recentes, integrações ativas, timelines configuradas e próximos passos recomendados. Chame sempre primeiro antes de qualquer outra ação.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ event_id: { type: 'string', description: 'ID do evento para diagnóstico específico (opcional)' },
37
+ },
38
+ },
39
+ },
40
+ // ── Eventos ──
41
+ {
42
+ name: 'list_events',
43
+ description: 'Lista todos os eventos do produtor com status, datas e configurações básicas.',
44
+ inputSchema: { type: 'object', properties: {} },
45
+ },
46
+ {
47
+ name: 'get_event',
48
+ description: 'Retorna todos os detalhes de um evento específico.',
49
+ inputSchema: {
50
+ type: 'object',
51
+ properties: { event_id: { type: 'string', description: 'ID do evento' } },
52
+ required: ['event_id'],
53
+ },
54
+ },
55
+ {
56
+ name: 'get_event_stats',
57
+ description: 'Retorna métricas do evento: inscrições, presença, pitch, cliques no CTA e taxa de conversão.',
58
+ inputSchema: {
59
+ type: 'object',
60
+ properties: { event_id: { type: 'string', description: 'ID do evento' } },
61
+ required: ['event_id'],
62
+ },
63
+ },
64
+ {
65
+ name: 'create_event',
66
+ description: 'Cria um novo evento ao vivo. Campos obrigatórios: title. Recomendado preencher youtube_url, event_start e cta_url.',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ title: { type: 'string', description: 'Título do evento' },
71
+ youtube_url: { type: 'string', description: 'URL do vídeo no YouTube (ex: https://youtu.be/xxx)' },
72
+ event_start: { type: 'string', description: 'Data/hora de início ISO 8601 (ex: 2025-09-10T20:00:00-03:00)' },
73
+ event_end: { type: 'string', description: 'Data/hora de encerramento ISO 8601' },
74
+ pitch_start: { type: 'string', description: 'Início do pitch de vendas ISO 8601' },
75
+ pitch_end: { type: 'string', description: 'Fim do pitch ISO 8601' },
76
+ cta_url: { type: 'string', description: 'URL do botão de compra/CTA' },
77
+ cta_label: { type: 'string', description: 'Texto do botão CTA (ex: "Garantir minha vaga")' },
78
+ },
79
+ required: ['title'],
80
+ },
81
+ },
82
+ {
83
+ name: 'update_event',
84
+ description: 'Edita campos de um evento existente. Passe apenas os campos que deseja alterar.',
85
+ inputSchema: {
86
+ type: 'object',
87
+ properties: {
88
+ event_id: { type: 'string' },
89
+ title: { type: 'string' },
90
+ youtube_url: { type: 'string' },
91
+ event_start: { type: 'string' },
92
+ event_end: { type: 'string' },
93
+ pitch_start: { type: 'string' },
94
+ pitch_end: { type: 'string' },
95
+ cta_url: { type: 'string' },
96
+ cta_label: { type: 'string' },
97
+ cta_enabled: { type: 'boolean', description: 'Ativa ou desativa o CTA' },
98
+ status: { type: 'string', enum: ['active', 'ended'] },
99
+ },
100
+ required: ['event_id'],
101
+ },
102
+ },
103
+ // ── Comunicações ──
104
+ {
105
+ name: 'list_timelines',
106
+ description: 'Lista todas as timelines de automação configuradas, com suas etapas.',
107
+ inputSchema: { type: 'object', properties: {} },
108
+ },
109
+ {
110
+ name: 'apply_timeline',
111
+ description: 'Aplica uma timeline a um evento, criando todas as automações de uma vez. Use após criar o evento.',
112
+ inputSchema: {
113
+ type: 'object',
114
+ properties: {
115
+ event_id: { type: 'string', description: 'ID do evento' },
116
+ timeline_id: { type: 'string', description: 'ID da timeline' },
117
+ },
118
+ required: ['event_id', 'timeline_id'],
119
+ },
120
+ },
121
+ {
122
+ name: 'list_automations',
123
+ description: 'Lista automações de um evento. Filtre por event_id para ver o que está ativo.',
124
+ inputSchema: {
125
+ type: 'object',
126
+ properties: {
127
+ event_id: { type: 'string', description: 'Filtra por evento (opcional)' },
128
+ },
129
+ },
130
+ },
131
+ {
132
+ name: 'create_automation',
133
+ description: 'Cria uma automação individual em um evento. Prefira apply_timeline para configurar tudo de uma vez.',
134
+ inputSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ event_id: { type: 'string' },
138
+ label: { type: 'string', description: 'Nome descritivo da automação' },
139
+ channel: { type: 'string', enum: ['whatsapp', 'email'] },
140
+ trigger: { type: 'string', enum: ['registration', 'event_start', 'event_end', 'attended', 'no_show', 'watched_pitch', 'clicked_cta', 'purchased', 'scheduled'] },
141
+ delay_minutes: { type: 'number', description: 'Minutos após o gatilho (negativo = antes)' },
142
+ template_id: { type: 'string', description: 'ID do template WhatsApp' },
143
+ broadcast_template_id: { type: 'string', description: 'ID do template de e-mail' },
144
+ send_time: { type: 'string', description: 'Ancora horário HH:MM (ex: "08:00")' },
145
+ scheduled_at: { type: 'string', description: 'Data/hora absoluta ISO 8601 (para trigger=scheduled)' },
146
+ audience_list_id: { type: 'string', description: 'ID da lista de destinatários' },
147
+ exclude_list_id: { type: 'string', description: 'ID da lista de exclusão' },
148
+ },
149
+ required: ['event_id', 'channel', 'trigger'],
150
+ },
151
+ },
152
+ {
153
+ name: 'update_automation',
154
+ description: 'Edita uma automação existente. Use para ativar/desativar ou ajustar timing.',
155
+ inputSchema: {
156
+ type: 'object',
157
+ properties: {
158
+ automation_id: { type: 'string' },
159
+ label: { type: 'string' },
160
+ delay_minutes: { type: 'number' },
161
+ send_time: { type: 'string' },
162
+ scheduled_at: { type: 'string' },
163
+ active: { type: 'boolean' },
164
+ },
165
+ required: ['automation_id'],
166
+ },
167
+ },
168
+ {
169
+ name: 'delete_automation',
170
+ description: 'Remove uma automação de um evento.',
171
+ inputSchema: {
172
+ type: 'object',
173
+ properties: {
174
+ automation_id: { type: 'string' },
175
+ },
176
+ required: ['automation_id'],
177
+ },
178
+ },
179
+ {
180
+ name: 'list_templates',
181
+ description: 'Lista templates disponíveis de WhatsApp e e-mail. Filtre por channel se necessário.',
182
+ inputSchema: {
183
+ type: 'object',
184
+ properties: {
185
+ channel: { type: 'string', enum: ['whatsapp', 'email'], description: 'Filtra por canal (opcional)' },
186
+ },
187
+ },
188
+ },
189
+ // ── Dados ──
190
+ {
191
+ name: 'list_contacts',
192
+ description: 'Lista contatos do produtor com paginação.',
193
+ inputSchema: {
194
+ type: 'object',
195
+ properties: {
196
+ limit: { type: 'number', description: 'Máximo de resultados (padrão 100, máximo 500)' },
197
+ offset: { type: 'number', description: 'Paginação' },
198
+ },
199
+ },
200
+ },
201
+ {
202
+ name: 'list_contact_lists',
203
+ description: 'Lista as listas de contatos geradas automaticamente após eventos (participantes, pitch, CTA, no-show).',
204
+ inputSchema: { type: 'object', properties: {} },
205
+ },
206
+ {
207
+ name: 'list_integrations',
208
+ description: 'Mostra quais integrações estão ativas (WhatsApp, e-mail, CRM, analytics) e as capacidades disponíveis.',
209
+ inputSchema: { type: 'object', properties: {} },
210
+ },
211
+ ];
212
+ // ── Prompts (guias de fluxo) ─────────────────────────────────────────────────
213
+ const PROMPTS = [
214
+ {
215
+ name: 'setup_event',
216
+ description: 'Guia completo para configurar um evento do zero: dados, CTA, automações.',
217
+ arguments: [
218
+ { name: 'event_name', description: 'Nome do evento a criar', required: true },
219
+ ],
220
+ },
221
+ {
222
+ name: 'post_event_followup',
223
+ description: 'Configura automações de follow-up após um evento encerrado (no-show, participantes, compradores).',
224
+ arguments: [
225
+ { name: 'event_id', description: 'ID do evento encerrado', required: true },
226
+ ],
227
+ },
228
+ ];
229
+ // ── Server ───────────────────────────────────────────────────────────────────
230
+ const server = new index_js_1.Server({ name: 'jotae-mcp', version: '1.0.0' }, { capabilities: { tools: {}, prompts: {} } });
231
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: TOOLS }));
232
+ server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
233
+ server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (req) => {
234
+ const { name, arguments: args } = req.params;
235
+ if (name === 'setup_event') {
236
+ const eventName = args?.event_name ?? 'novo evento';
237
+ return {
238
+ messages: [{
239
+ role: 'user',
240
+ content: {
241
+ type: 'text',
242
+ text: `Quero configurar o evento "${eventName}" no Jotae.
243
+
244
+ Siga estas etapas em ordem:
245
+ 1. Chame get_setup_status para entender o estado atual da conta
246
+ 2. Crie o evento com create_event (título, data, YouTube, CTA)
247
+ 3. Chame list_integrations para saber quais canais estão disponíveis
248
+ 4. Chame list_timelines para ver timelines existentes
249
+ 5. Se houver timeline adequada, aplique com apply_timeline
250
+ 6. Se não, chame list_templates e crie automações individuais com create_automation
251
+ 7. Confirme o resultado com get_event e list_automations
252
+
253
+ Seja direto: pergunte apenas o que não consegue inferir.`,
254
+ },
255
+ }],
256
+ };
257
+ }
258
+ if (name === 'post_event_followup') {
259
+ const eventId = args?.event_id ?? '';
260
+ return {
261
+ messages: [{
262
+ role: 'user',
263
+ content: {
264
+ type: 'text',
265
+ text: `Quero configurar o follow-up pós-evento para o evento ${eventId}.
266
+
267
+ Etapas:
268
+ 1. Chame get_event_stats para ver quem participou, quem viu o pitch e quem clicou no CTA
269
+ 2. Chame list_contact_lists para ver as listas geradas (participantes, no-show, pitch, CTA)
270
+ 3. Chame list_integrations para confirmar canais disponíveis
271
+ 4. Chame list_templates para ver mensagens disponíveis
272
+ 5. Crie automações segmentadas com create_automation:
273
+ - Participantes que NÃO clicaram no CTA → lembrete de oferta
274
+ - No-show → mensagem de replay ou segunda chance
275
+ - Compradores (se tiver integração Hotmart) → boas-vindas
276
+ 6. Confirme com list_automations
277
+
278
+ Não pergunte o que já sabe pelas chamadas.`,
279
+ },
280
+ }],
281
+ };
282
+ }
283
+ throw new Error(`Prompt "${name}" não encontrado`);
284
+ });
285
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
286
+ const { name, arguments: args } = req.params;
287
+ const a = (args ?? {});
288
+ try {
289
+ let result;
290
+ switch (name) {
291
+ case 'get_setup_status':
292
+ result = await api('GET', `/setup-status${a.event_id ? `?event_id=${a.event_id}` : ''}`);
293
+ break;
294
+ case 'list_events':
295
+ result = await api('GET', '/events');
296
+ break;
297
+ case 'get_event':
298
+ result = await api('GET', `/events/${a.event_id}`);
299
+ break;
300
+ case 'get_event_stats':
301
+ result = await api('GET', `/events/${a.event_id}/stats`);
302
+ break;
303
+ case 'create_event':
304
+ result = await api('POST', '/events', a);
305
+ break;
306
+ case 'update_event': {
307
+ const { event_id, ...patch } = a;
308
+ result = await api('PATCH', `/events/${event_id}`, patch);
309
+ break;
310
+ }
311
+ case 'list_timelines':
312
+ result = await api('GET', '/timelines');
313
+ break;
314
+ case 'apply_timeline':
315
+ result = await api('POST', `/events/${a.event_id}/apply-timeline`, { timeline_id: a.timeline_id });
316
+ break;
317
+ case 'list_automations':
318
+ result = await api('GET', `/automations${a.event_id ? `?event_id=${a.event_id}` : ''}`);
319
+ break;
320
+ case 'create_automation':
321
+ result = await api('POST', '/automations', a);
322
+ break;
323
+ case 'update_automation': {
324
+ const { automation_id, ...patch } = a;
325
+ result = await api('PATCH', `/automations/${automation_id}`, patch);
326
+ break;
327
+ }
328
+ case 'delete_automation':
329
+ result = await api('DELETE', `/automations/${a.automation_id}`);
330
+ break;
331
+ case 'list_templates':
332
+ result = await api('GET', `/templates${a.channel ? `?channel=${a.channel}` : ''}`);
333
+ break;
334
+ case 'list_contacts':
335
+ result = await api('GET', `/contacts?limit=${a.limit ?? 100}&offset=${a.offset ?? 0}`);
336
+ break;
337
+ case 'list_contact_lists':
338
+ result = await api('GET', '/lists');
339
+ break;
340
+ case 'list_integrations':
341
+ result = await api('GET', '/integrations');
342
+ break;
343
+ default:
344
+ throw new Error(`Tool desconhecida: ${name}`);
345
+ }
346
+ return {
347
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
348
+ };
349
+ }
350
+ catch (e) {
351
+ return {
352
+ content: [{ type: 'text', text: `Erro: ${e.message}` }],
353
+ isError: true,
354
+ };
355
+ }
356
+ });
357
+ async function main() {
358
+ const transport = new stdio_js_1.StdioServerTransport();
359
+ await server.connect(transport);
360
+ process.stderr.write('Jotae MCP Server iniciado\n');
361
+ }
362
+ main().catch(e => {
363
+ process.stderr.write(`Erro fatal: ${e}\n`);
364
+ process.exit(1);
365
+ });
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "jotae-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server para o Jotae — cria eventos, configura automações e lê métricas via Claude",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "jotae-mcp": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "@modelcontextprotocol/sdk": "^1.0.0",
16
+ "zod": "^3.22.4"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^20.0.0",
20
+ "ts-node": "^10.9.2",
21
+ "typescript": "^5.3.0"
22
+ },
23
+ "keywords": ["mcp", "jotae", "claude", "ai", "events", "whatsapp"],
24
+ "license": "MIT"
25
+ }
package/src/index.ts ADDED
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ ListPromptsRequestSchema,
8
+ GetPromptRequestSchema,
9
+ } from '@modelcontextprotocol/sdk/types.js'
10
+ import { z } from 'zod'
11
+
12
+ const BASE_URL = process.env.JOTAE_BASE_URL ?? 'https://app.jotae.me'
13
+ const API_KEY = process.env.JOTAE_API_KEY ?? ''
14
+
15
+ if (!API_KEY) {
16
+ process.stderr.write('JOTAE_API_KEY não configurada. Defina a variável de ambiente.\n')
17
+ process.exit(1)
18
+ }
19
+
20
+ async function api(method: string, path: string, body?: unknown) {
21
+ const res = await fetch(`${BASE_URL}/api/v1${path}`, {
22
+ method,
23
+ headers: {
24
+ 'Authorization': `Bearer ${API_KEY}`,
25
+ 'Content-Type': 'application/json',
26
+ },
27
+ body: body ? JSON.stringify(body) : undefined,
28
+ })
29
+ const json = await res.json() as Record<string, unknown>
30
+ if (!res.ok) throw new Error((json.error as string) ?? `HTTP ${res.status}`)
31
+ return json
32
+ }
33
+
34
+ // ── Tools ────────────────────────────────────────────────────────────────────
35
+
36
+ const TOOLS = [
37
+ // ── Diagnóstico ──
38
+ {
39
+ name: 'get_setup_status',
40
+ description: 'Retorna o estado geral da conta: eventos recentes, integrações ativas, timelines configuradas e próximos passos recomendados. Chame sempre primeiro antes de qualquer outra ação.',
41
+ inputSchema: {
42
+ type: 'object' as const,
43
+ properties: {
44
+ event_id: { type: 'string', description: 'ID do evento para diagnóstico específico (opcional)' },
45
+ },
46
+ },
47
+ },
48
+
49
+ // ── Eventos ──
50
+ {
51
+ name: 'list_events',
52
+ description: 'Lista todos os eventos do produtor com status, datas e configurações básicas.',
53
+ inputSchema: { type: 'object' as const, properties: {} },
54
+ },
55
+ {
56
+ name: 'get_event',
57
+ description: 'Retorna todos os detalhes de um evento específico.',
58
+ inputSchema: {
59
+ type: 'object' as const,
60
+ properties: { event_id: { type: 'string', description: 'ID do evento' } },
61
+ required: ['event_id'],
62
+ },
63
+ },
64
+ {
65
+ name: 'get_event_stats',
66
+ description: 'Retorna métricas do evento: inscrições, presença, pitch, cliques no CTA e taxa de conversão.',
67
+ inputSchema: {
68
+ type: 'object' as const,
69
+ properties: { event_id: { type: 'string', description: 'ID do evento' } },
70
+ required: ['event_id'],
71
+ },
72
+ },
73
+ {
74
+ name: 'create_event',
75
+ description: 'Cria um novo evento ao vivo. Campos obrigatórios: title. Recomendado preencher youtube_url, event_start e cta_url.',
76
+ inputSchema: {
77
+ type: 'object' as const,
78
+ properties: {
79
+ title: { type: 'string', description: 'Título do evento' },
80
+ youtube_url: { type: 'string', description: 'URL do vídeo no YouTube (ex: https://youtu.be/xxx)' },
81
+ event_start: { type: 'string', description: 'Data/hora de início ISO 8601 (ex: 2025-09-10T20:00:00-03:00)' },
82
+ event_end: { type: 'string', description: 'Data/hora de encerramento ISO 8601' },
83
+ pitch_start: { type: 'string', description: 'Início do pitch de vendas ISO 8601' },
84
+ pitch_end: { type: 'string', description: 'Fim do pitch ISO 8601' },
85
+ cta_url: { type: 'string', description: 'URL do botão de compra/CTA' },
86
+ cta_label: { type: 'string', description: 'Texto do botão CTA (ex: "Garantir minha vaga")' },
87
+ },
88
+ required: ['title'],
89
+ },
90
+ },
91
+ {
92
+ name: 'update_event',
93
+ description: 'Edita campos de um evento existente. Passe apenas os campos que deseja alterar.',
94
+ inputSchema: {
95
+ type: 'object' as const,
96
+ properties: {
97
+ event_id: { type: 'string' },
98
+ title: { type: 'string' },
99
+ youtube_url: { type: 'string' },
100
+ event_start: { type: 'string' },
101
+ event_end: { type: 'string' },
102
+ pitch_start: { type: 'string' },
103
+ pitch_end: { type: 'string' },
104
+ cta_url: { type: 'string' },
105
+ cta_label: { type: 'string' },
106
+ cta_enabled: { type: 'boolean', description: 'Ativa ou desativa o CTA' },
107
+ status: { type: 'string', enum: ['active', 'ended'] },
108
+ },
109
+ required: ['event_id'],
110
+ },
111
+ },
112
+
113
+ // ── Comunicações ──
114
+ {
115
+ name: 'list_timelines',
116
+ description: 'Lista todas as timelines de automação configuradas, com suas etapas.',
117
+ inputSchema: { type: 'object' as const, properties: {} },
118
+ },
119
+ {
120
+ name: 'apply_timeline',
121
+ description: 'Aplica uma timeline a um evento, criando todas as automações de uma vez. Use após criar o evento.',
122
+ inputSchema: {
123
+ type: 'object' as const,
124
+ properties: {
125
+ event_id: { type: 'string', description: 'ID do evento' },
126
+ timeline_id: { type: 'string', description: 'ID da timeline' },
127
+ },
128
+ required: ['event_id', 'timeline_id'],
129
+ },
130
+ },
131
+ {
132
+ name: 'list_automations',
133
+ description: 'Lista automações de um evento. Filtre por event_id para ver o que está ativo.',
134
+ inputSchema: {
135
+ type: 'object' as const,
136
+ properties: {
137
+ event_id: { type: 'string', description: 'Filtra por evento (opcional)' },
138
+ },
139
+ },
140
+ },
141
+ {
142
+ name: 'create_automation',
143
+ description: 'Cria uma automação individual em um evento. Prefira apply_timeline para configurar tudo de uma vez.',
144
+ inputSchema: {
145
+ type: 'object' as const,
146
+ properties: {
147
+ event_id: { type: 'string' },
148
+ label: { type: 'string', description: 'Nome descritivo da automação' },
149
+ channel: { type: 'string', enum: ['whatsapp', 'email'] },
150
+ trigger: { type: 'string', enum: ['registration', 'event_start', 'event_end', 'attended', 'no_show', 'watched_pitch', 'clicked_cta', 'purchased', 'scheduled'] },
151
+ delay_minutes: { type: 'number', description: 'Minutos após o gatilho (negativo = antes)' },
152
+ template_id: { type: 'string', description: 'ID do template WhatsApp' },
153
+ broadcast_template_id:{ type: 'string', description: 'ID do template de e-mail' },
154
+ send_time: { type: 'string', description: 'Ancora horário HH:MM (ex: "08:00")' },
155
+ scheduled_at: { type: 'string', description: 'Data/hora absoluta ISO 8601 (para trigger=scheduled)' },
156
+ audience_list_id: { type: 'string', description: 'ID da lista de destinatários' },
157
+ exclude_list_id: { type: 'string', description: 'ID da lista de exclusão' },
158
+ },
159
+ required: ['event_id', 'channel', 'trigger'],
160
+ },
161
+ },
162
+ {
163
+ name: 'update_automation',
164
+ description: 'Edita uma automação existente. Use para ativar/desativar ou ajustar timing.',
165
+ inputSchema: {
166
+ type: 'object' as const,
167
+ properties: {
168
+ automation_id: { type: 'string' },
169
+ label: { type: 'string' },
170
+ delay_minutes: { type: 'number' },
171
+ send_time: { type: 'string' },
172
+ scheduled_at: { type: 'string' },
173
+ active: { type: 'boolean' },
174
+ },
175
+ required: ['automation_id'],
176
+ },
177
+ },
178
+ {
179
+ name: 'delete_automation',
180
+ description: 'Remove uma automação de um evento.',
181
+ inputSchema: {
182
+ type: 'object' as const,
183
+ properties: {
184
+ automation_id: { type: 'string' },
185
+ },
186
+ required: ['automation_id'],
187
+ },
188
+ },
189
+ {
190
+ name: 'list_templates',
191
+ description: 'Lista templates disponíveis de WhatsApp e e-mail. Filtre por channel se necessário.',
192
+ inputSchema: {
193
+ type: 'object' as const,
194
+ properties: {
195
+ channel: { type: 'string', enum: ['whatsapp', 'email'], description: 'Filtra por canal (opcional)' },
196
+ },
197
+ },
198
+ },
199
+
200
+ // ── Dados ──
201
+ {
202
+ name: 'list_contacts',
203
+ description: 'Lista contatos do produtor com paginação.',
204
+ inputSchema: {
205
+ type: 'object' as const,
206
+ properties: {
207
+ limit: { type: 'number', description: 'Máximo de resultados (padrão 100, máximo 500)' },
208
+ offset: { type: 'number', description: 'Paginação' },
209
+ },
210
+ },
211
+ },
212
+ {
213
+ name: 'list_contact_lists',
214
+ description: 'Lista as listas de contatos geradas automaticamente após eventos (participantes, pitch, CTA, no-show).',
215
+ inputSchema: { type: 'object' as const, properties: {} },
216
+ },
217
+ {
218
+ name: 'list_integrations',
219
+ description: 'Mostra quais integrações estão ativas (WhatsApp, e-mail, CRM, analytics) e as capacidades disponíveis.',
220
+ inputSchema: { type: 'object' as const, properties: {} },
221
+ },
222
+ ]
223
+
224
+ // ── Prompts (guias de fluxo) ─────────────────────────────────────────────────
225
+
226
+ const PROMPTS = [
227
+ {
228
+ name: 'setup_event',
229
+ description: 'Guia completo para configurar um evento do zero: dados, CTA, automações.',
230
+ arguments: [
231
+ { name: 'event_name', description: 'Nome do evento a criar', required: true },
232
+ ],
233
+ },
234
+ {
235
+ name: 'post_event_followup',
236
+ description: 'Configura automações de follow-up após um evento encerrado (no-show, participantes, compradores).',
237
+ arguments: [
238
+ { name: 'event_id', description: 'ID do evento encerrado', required: true },
239
+ ],
240
+ },
241
+ ]
242
+
243
+ // ── Server ───────────────────────────────────────────────────────────────────
244
+
245
+ const server = new Server(
246
+ { name: 'jotae-mcp', version: '1.0.0' },
247
+ { capabilities: { tools: {}, prompts: {} } }
248
+ )
249
+
250
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }))
251
+
252
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }))
253
+
254
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
255
+ const { name, arguments: args } = req.params
256
+
257
+ if (name === 'setup_event') {
258
+ const eventName = args?.event_name ?? 'novo evento'
259
+ return {
260
+ messages: [{
261
+ role: 'user' as const,
262
+ content: {
263
+ type: 'text' as const,
264
+ text: `Quero configurar o evento "${eventName}" no Jotae.
265
+
266
+ Siga estas etapas em ordem:
267
+ 1. Chame get_setup_status para entender o estado atual da conta
268
+ 2. Crie o evento com create_event (título, data, YouTube, CTA)
269
+ 3. Chame list_integrations para saber quais canais estão disponíveis
270
+ 4. Chame list_timelines para ver timelines existentes
271
+ 5. Se houver timeline adequada, aplique com apply_timeline
272
+ 6. Se não, chame list_templates e crie automações individuais com create_automation
273
+ 7. Confirme o resultado com get_event e list_automations
274
+
275
+ Seja direto: pergunte apenas o que não consegue inferir.`,
276
+ },
277
+ }],
278
+ }
279
+ }
280
+
281
+ if (name === 'post_event_followup') {
282
+ const eventId = args?.event_id ?? ''
283
+ return {
284
+ messages: [{
285
+ role: 'user' as const,
286
+ content: {
287
+ type: 'text' as const,
288
+ text: `Quero configurar o follow-up pós-evento para o evento ${eventId}.
289
+
290
+ Etapas:
291
+ 1. Chame get_event_stats para ver quem participou, quem viu o pitch e quem clicou no CTA
292
+ 2. Chame list_contact_lists para ver as listas geradas (participantes, no-show, pitch, CTA)
293
+ 3. Chame list_integrations para confirmar canais disponíveis
294
+ 4. Chame list_templates para ver mensagens disponíveis
295
+ 5. Crie automações segmentadas com create_automation:
296
+ - Participantes que NÃO clicaram no CTA → lembrete de oferta
297
+ - No-show → mensagem de replay ou segunda chance
298
+ - Compradores (se tiver integração Hotmart) → boas-vindas
299
+ 6. Confirme com list_automations
300
+
301
+ Não pergunte o que já sabe pelas chamadas.`,
302
+ },
303
+ }],
304
+ }
305
+ }
306
+
307
+ throw new Error(`Prompt "${name}" não encontrado`)
308
+ })
309
+
310
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
311
+ const { name, arguments: args } = req.params
312
+ const a = (args ?? {}) as Record<string, unknown>
313
+
314
+ try {
315
+ let result: unknown
316
+
317
+ switch (name) {
318
+ case 'get_setup_status':
319
+ result = await api('GET', `/setup-status${a.event_id ? `?event_id=${a.event_id}` : ''}`)
320
+ break
321
+ case 'list_events':
322
+ result = await api('GET', '/events')
323
+ break
324
+ case 'get_event':
325
+ result = await api('GET', `/events/${a.event_id}`)
326
+ break
327
+ case 'get_event_stats':
328
+ result = await api('GET', `/events/${a.event_id}/stats`)
329
+ break
330
+ case 'create_event':
331
+ result = await api('POST', '/events', a)
332
+ break
333
+ case 'update_event': {
334
+ const { event_id, ...patch } = a
335
+ result = await api('PATCH', `/events/${event_id}`, patch)
336
+ break
337
+ }
338
+ case 'list_timelines':
339
+ result = await api('GET', '/timelines')
340
+ break
341
+ case 'apply_timeline':
342
+ result = await api('POST', `/events/${a.event_id}/apply-timeline`, { timeline_id: a.timeline_id })
343
+ break
344
+ case 'list_automations':
345
+ result = await api('GET', `/automations${a.event_id ? `?event_id=${a.event_id}` : ''}`)
346
+ break
347
+ case 'create_automation':
348
+ result = await api('POST', '/automations', a)
349
+ break
350
+ case 'update_automation': {
351
+ const { automation_id, ...patch } = a
352
+ result = await api('PATCH', `/automations/${automation_id}`, patch)
353
+ break
354
+ }
355
+ case 'delete_automation':
356
+ result = await api('DELETE', `/automations/${a.automation_id}`)
357
+ break
358
+ case 'list_templates':
359
+ result = await api('GET', `/templates${a.channel ? `?channel=${a.channel}` : ''}`)
360
+ break
361
+ case 'list_contacts':
362
+ result = await api('GET', `/contacts?limit=${a.limit ?? 100}&offset=${a.offset ?? 0}`)
363
+ break
364
+ case 'list_contact_lists':
365
+ result = await api('GET', '/lists')
366
+ break
367
+ case 'list_integrations':
368
+ result = await api('GET', '/integrations')
369
+ break
370
+ default:
371
+ throw new Error(`Tool desconhecida: ${name}`)
372
+ }
373
+
374
+ return {
375
+ content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],
376
+ }
377
+ } catch (e) {
378
+ return {
379
+ content: [{ type: 'text' as const, text: `Erro: ${(e as Error).message}` }],
380
+ isError: true,
381
+ }
382
+ }
383
+ })
384
+
385
+ async function main() {
386
+ const transport = new StdioServerTransport()
387
+ await server.connect(transport)
388
+ process.stderr.write('Jotae MCP Server iniciado\n')
389
+ }
390
+
391
+ main().catch(e => {
392
+ process.stderr.write(`Erro fatal: ${e}\n`)
393
+ process.exit(1)
394
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "resolveJsonModule": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }