jotae-mcp 1.0.0 → 1.0.1
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/package.json +2 -1
- package/src/index.ts +0 -394
- package/tsconfig.json +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jotae-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "MCP Server para o Jotae — cria eventos, configura automações e lê métricas via Claude",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"ts-node": "^10.9.2",
|
|
21
21
|
"typescript": "^5.3.0"
|
|
22
22
|
},
|
|
23
|
+
"files": ["dist", "README.md"],
|
|
23
24
|
"keywords": ["mcp", "jotae", "claude", "ai", "events", "whatsapp"],
|
|
24
25
|
"license": "MIT"
|
|
25
26
|
}
|
package/src/index.ts
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
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
|
-
}
|