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 +101 -0
- package/dist/index.js +365 -0
- package/package.json +25 -0
- package/src/index.ts +394 -0
- package/tsconfig.json +15 -0
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
|
+
}
|