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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jotae-mcp",
3
- "version": "1.0.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
- }