mirlo-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 +171 -0
- package/dist/client.d.ts +37 -0
- package/dist/client.js +126 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +76 -0
- package/dist/tools.d.ts +12 -0
- package/dist/tools.js +286 -0
- package/package.json +31 -0
- package/src/client.ts +167 -0
- package/src/index.ts +90 -0
- package/src/tools.ts +328 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Mirlo para Claude
|
|
2
|
+
|
|
3
|
+
Conectá tu CRM de Mirlo con Claude para consultar y gestionar oportunidades, contactos, tickets y tareas usando lenguaje natural.
|
|
4
|
+
|
|
5
|
+
## ¿Qué puedo hacer?
|
|
6
|
+
|
|
7
|
+
Preguntale a Claude cosas como:
|
|
8
|
+
|
|
9
|
+
- *"¿Cuántos deals abiertos tengo arriba de $100k?"*
|
|
10
|
+
- *"Creame un ticket de soporte con prioridad alta"*
|
|
11
|
+
- *"¿Qué tareas tengo vencidas?"*
|
|
12
|
+
- *"Dame los contactos de la empresa Farmacias del Ahorro"*
|
|
13
|
+
- *"¿Cuáles oportunidades cierran esta semana?"*
|
|
14
|
+
- *"Creá una tarea para llamar al cliente mañana"*
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Configuración paso a paso
|
|
19
|
+
|
|
20
|
+
### Paso 1: Obtené tu API Key de Mirlo
|
|
21
|
+
|
|
22
|
+
1. Ingresá a [app.mirlo.com](https://app.mirlo.com)
|
|
23
|
+
2. Andá a **Mi Organización** → **API Keys**
|
|
24
|
+
3. Hacé click en **+ Crear API Key**
|
|
25
|
+
4. Copiá la key (empieza con `sk_live_`)
|
|
26
|
+
|
|
27
|
+
### Paso 2: Instalá Node.js (si no lo tenés)
|
|
28
|
+
|
|
29
|
+
Descargá e instalá Node.js desde [nodejs.org](https://nodejs.org). Elegí la versión LTS (recomendada).
|
|
30
|
+
|
|
31
|
+
Para verificar que está instalado, abrí la Terminal y escribí:
|
|
32
|
+
```
|
|
33
|
+
node --version
|
|
34
|
+
```
|
|
35
|
+
Debería mostrar algo como `v22.x.x`.
|
|
36
|
+
|
|
37
|
+
### Paso 3: Configurá Claude Desktop
|
|
38
|
+
|
|
39
|
+
1. Abrí **Claude Desktop**
|
|
40
|
+
2. Andá a **Settings** (Configuración) → **Developer** → **Edit Config**
|
|
41
|
+
3. Se abre un archivo. Reemplazá el contenido con:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"mirlo": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "@mirlo/mcp", "--api-key=TU_API_KEY_AQUI"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
4. Reemplazá `TU_API_KEY_AQUI` con tu API key de Mirlo (la que copiaste en el Paso 1)
|
|
55
|
+
5. Guardá el archivo y **reiniciá Claude Desktop**
|
|
56
|
+
|
|
57
|
+
### Paso 4: ¡Listo!
|
|
58
|
+
|
|
59
|
+
Abrí una nueva conversación en Claude y preguntá algo sobre tu CRM:
|
|
60
|
+
|
|
61
|
+
> *"¿Cuántos tickets abiertos tengo?"*
|
|
62
|
+
|
|
63
|
+
Claude va a usar las herramientas de Mirlo automáticamente para responder.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Configuración con Claude Code (CLI)
|
|
68
|
+
|
|
69
|
+
Si usás Claude Code en la terminal:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Agregar el MCP server
|
|
73
|
+
claude mcp add mirlo npx -y @mirlo/mcp --api-key=sk_live_xxx
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ¿Qué puede hacer?
|
|
79
|
+
|
|
80
|
+
### CRM — Oportunidades
|
|
81
|
+
| Acción | Ejemplo |
|
|
82
|
+
|--------|---------|
|
|
83
|
+
| Listar deals | *"Dame las oportunidades abiertas ordenadas por monto"* |
|
|
84
|
+
| Buscar deals | *"¿Hay deals que cierran esta semana?"* |
|
|
85
|
+
| Crear deal | *"Creá una oportunidad para Acme Corp por $50,000"* |
|
|
86
|
+
| Mover de etapa | *"Mové el deal de Acme a Negociación"* |
|
|
87
|
+
| Filtrar por tiempo | *"¿Qué deals llevan más de 24 horas en Prospecto?"* |
|
|
88
|
+
|
|
89
|
+
### CRM — Contactos y Cuentas
|
|
90
|
+
| Acción | Ejemplo |
|
|
91
|
+
|--------|---------|
|
|
92
|
+
| Buscar contactos | *"Buscame los contactos de la empresa Café Digital"* |
|
|
93
|
+
| Crear contacto | *"Creá un contacto: Ana García, ana@empresa.com"* |
|
|
94
|
+
| Listar cuentas | *"¿Qué cuentas tenemos en la industria Tecnología?"* |
|
|
95
|
+
|
|
96
|
+
### CRM — Tickets
|
|
97
|
+
| Acción | Ejemplo |
|
|
98
|
+
|--------|---------|
|
|
99
|
+
| Ver tickets abiertos | *"¿Cuántos tickets abiertos hay?"* |
|
|
100
|
+
| Crear ticket | *"Creá un ticket urgente: Error en la integración"* |
|
|
101
|
+
| Filtrar por prioridad | *"Mostrá los tickets de prioridad alta"* |
|
|
102
|
+
|
|
103
|
+
### CRM — Tareas
|
|
104
|
+
| Acción | Ejemplo |
|
|
105
|
+
|--------|---------|
|
|
106
|
+
| Ver tareas pendientes | *"¿Qué tareas tengo para hoy?"* |
|
|
107
|
+
| Tareas vencidas | *"¿Hay tareas vencidas?"* |
|
|
108
|
+
| Crear tarea | *"Creá una tarea para llamar a Ana García mañana"* |
|
|
109
|
+
| Completar tarea | *"Marcá como completada la tarea de llamar a Ana"* |
|
|
110
|
+
|
|
111
|
+
### Messaging
|
|
112
|
+
| Acción | Ejemplo |
|
|
113
|
+
|--------|---------|
|
|
114
|
+
| Enviar mensaje | *"Mandá un WhatsApp a +52 55 1234 5678 diciendo: Hola, te contacto de Mirlo"* |
|
|
115
|
+
| Ver conversaciones | *"Mostrá las últimas conversaciones"* |
|
|
116
|
+
|
|
117
|
+
### Voz
|
|
118
|
+
| Acción | Ejemplo |
|
|
119
|
+
|--------|---------|
|
|
120
|
+
| Ver llamadas | *"¿Cuántas llamadas entrantes hubo hoy?"* |
|
|
121
|
+
| Transcripción | *"Dame la transcripción de la última llamada"* |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Solución de problemas
|
|
126
|
+
|
|
127
|
+
### "Claude no encuentra las herramientas de Mirlo"
|
|
128
|
+
- Verificá que reiniciaste Claude Desktop después de editar la configuración
|
|
129
|
+
- Verificá que tu API Key es válida en [app.mirlo.com](https://app.mirlo.com) → API Keys
|
|
130
|
+
|
|
131
|
+
### "Error de autenticación"
|
|
132
|
+
- Tu API Key puede haber expirado. Generá una nueva en Mi Organización → API Keys
|
|
133
|
+
|
|
134
|
+
### "npx no se encuentra"
|
|
135
|
+
- Instalá Node.js desde [nodejs.org](https://nodejs.org)
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Soporte
|
|
140
|
+
|
|
141
|
+
¿Necesitás ayuda? Contactanos en [mirlo.com](https://mirlo.com) o por WhatsApp.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Para desarrolladores
|
|
146
|
+
|
|
147
|
+
### Instalación manual
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm install -g @mirlo/mcp
|
|
151
|
+
mirlo-mcp --api-key=sk_live_xxx
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Variables de entorno
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
MIRLO_API_KEY=sk_live_xxx # API key (requerida)
|
|
158
|
+
MIRLO_BASE_URL=https://... # URL base (opcional, default: api.mirlo.com)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Herramientas disponibles (21 tools)
|
|
162
|
+
|
|
163
|
+
**CRM:** `mirlo_list_deals`, `mirlo_get_deal`, `mirlo_create_deal`, `mirlo_update_deal`, `mirlo_move_deal_stage`, `mirlo_list_contacts`, `mirlo_get_contact`, `mirlo_create_contact`, `mirlo_list_accounts`, `mirlo_create_account`, `mirlo_list_tickets`, `mirlo_create_ticket`, `mirlo_list_tasks`, `mirlo_create_task`, `mirlo_complete_task`, `mirlo_list_pipelines`
|
|
164
|
+
|
|
165
|
+
**Messaging:** `mirlo_send_message`, `mirlo_send_template`, `mirlo_list_conversations`
|
|
166
|
+
|
|
167
|
+
**Voice:** `mirlo_list_calls`, `mirlo_get_call_transcript`
|
|
168
|
+
|
|
169
|
+
## Licencia
|
|
170
|
+
|
|
171
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirlo API Client — wraps the public API for CRM, Messaging, and Voice.
|
|
3
|
+
*/
|
|
4
|
+
export declare class MirloClient {
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
private readonly apiKey;
|
|
7
|
+
constructor(apiKey: string, baseUrl?: string);
|
|
8
|
+
private request;
|
|
9
|
+
listDeals(filters?: Record<string, string | number | boolean>): Promise<any>;
|
|
10
|
+
getDeal(id: string): Promise<any>;
|
|
11
|
+
createDeal(data: Record<string, unknown>): Promise<any>;
|
|
12
|
+
updateDeal(id: string, data: Record<string, unknown>): Promise<any>;
|
|
13
|
+
moveDealStage(id: string, stageId: string): Promise<any>;
|
|
14
|
+
deleteDeal(id: string): Promise<any>;
|
|
15
|
+
listContacts(filters?: Record<string, string>): Promise<any>;
|
|
16
|
+
getContact(id: string): Promise<any>;
|
|
17
|
+
createContact(data: Record<string, unknown>): Promise<any>;
|
|
18
|
+
updateContact(id: string, data: Record<string, unknown>): Promise<any>;
|
|
19
|
+
listAccounts(filters?: Record<string, string>): Promise<any>;
|
|
20
|
+
getAccount(id: string): Promise<any>;
|
|
21
|
+
createAccount(data: Record<string, unknown>): Promise<any>;
|
|
22
|
+
updateAccount(id: string, data: Record<string, unknown>): Promise<any>;
|
|
23
|
+
listTickets(filters?: Record<string, string>): Promise<any>;
|
|
24
|
+
getTicket(id: string): Promise<any>;
|
|
25
|
+
createTicket(data: Record<string, unknown>): Promise<any>;
|
|
26
|
+
updateTicket(id: string, data: Record<string, unknown>): Promise<any>;
|
|
27
|
+
listTasks(filters?: Record<string, string>): Promise<any>;
|
|
28
|
+
createTask(data: Record<string, unknown>): Promise<any>;
|
|
29
|
+
completeTask(id: string): Promise<any>;
|
|
30
|
+
listPipelines(): Promise<any>;
|
|
31
|
+
sendMessage(to: string, body: string, channel?: string): Promise<any>;
|
|
32
|
+
sendTemplate(to: string, templateId: string, params?: Record<string, string>): Promise<any>;
|
|
33
|
+
listConversations(filters?: Record<string, string>): Promise<any>;
|
|
34
|
+
listMessages(conversationId: string, limit?: number): Promise<any>;
|
|
35
|
+
listCalls(filters?: Record<string, string>): Promise<any>;
|
|
36
|
+
getCallTranscript(callId: string): Promise<any>;
|
|
37
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirlo API Client — wraps the public API for CRM, Messaging, and Voice.
|
|
3
|
+
*/
|
|
4
|
+
export class MirloClient {
|
|
5
|
+
baseUrl;
|
|
6
|
+
apiKey;
|
|
7
|
+
constructor(apiKey, baseUrl = 'https://api.mirlo.com/v1') {
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
this.baseUrl = baseUrl;
|
|
10
|
+
}
|
|
11
|
+
async request(method, path, body) {
|
|
12
|
+
const url = `${this.baseUrl}${path}`;
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
method,
|
|
15
|
+
headers: {
|
|
16
|
+
'X-API-Key': this.apiKey,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const text = await res.text();
|
|
23
|
+
throw new Error(`Mirlo API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
|
|
24
|
+
}
|
|
25
|
+
return res.json();
|
|
26
|
+
}
|
|
27
|
+
// ── CRM: Deals ──
|
|
28
|
+
async listDeals(filters) {
|
|
29
|
+
const params = filters ? '?' + new URLSearchParams(Object.entries(filters).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
30
|
+
return this.request('GET', `/crm/deals${params}`);
|
|
31
|
+
}
|
|
32
|
+
async getDeal(id) {
|
|
33
|
+
return this.request('GET', `/crm/deals/${id}`);
|
|
34
|
+
}
|
|
35
|
+
async createDeal(data) {
|
|
36
|
+
return this.request('POST', '/crm/deals', data);
|
|
37
|
+
}
|
|
38
|
+
async updateDeal(id, data) {
|
|
39
|
+
return this.request('PATCH', `/crm/deals/${id}`, data);
|
|
40
|
+
}
|
|
41
|
+
async moveDealStage(id, stageId) {
|
|
42
|
+
return this.request('PATCH', `/crm/deals/${id}/stage`, { stage_id: stageId });
|
|
43
|
+
}
|
|
44
|
+
async deleteDeal(id) {
|
|
45
|
+
return this.request('DELETE', `/crm/deals/${id}`);
|
|
46
|
+
}
|
|
47
|
+
// ── CRM: Contacts ──
|
|
48
|
+
async listContacts(filters) {
|
|
49
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
50
|
+
return this.request('GET', `/crm/contacts${params}`);
|
|
51
|
+
}
|
|
52
|
+
async getContact(id) {
|
|
53
|
+
return this.request('GET', `/crm/contacts/${id}`);
|
|
54
|
+
}
|
|
55
|
+
async createContact(data) {
|
|
56
|
+
return this.request('POST', '/crm/contacts', data);
|
|
57
|
+
}
|
|
58
|
+
async updateContact(id, data) {
|
|
59
|
+
return this.request('PATCH', `/crm/contacts/${id}`, data);
|
|
60
|
+
}
|
|
61
|
+
// ── CRM: Accounts ──
|
|
62
|
+
async listAccounts(filters) {
|
|
63
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
64
|
+
return this.request('GET', `/crm/accounts${params}`);
|
|
65
|
+
}
|
|
66
|
+
async getAccount(id) {
|
|
67
|
+
return this.request('GET', `/crm/accounts/${id}`);
|
|
68
|
+
}
|
|
69
|
+
async createAccount(data) {
|
|
70
|
+
return this.request('POST', '/crm/accounts', data);
|
|
71
|
+
}
|
|
72
|
+
async updateAccount(id, data) {
|
|
73
|
+
return this.request('PATCH', `/crm/accounts/${id}`, data);
|
|
74
|
+
}
|
|
75
|
+
// ── CRM: Tickets ──
|
|
76
|
+
async listTickets(filters) {
|
|
77
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
78
|
+
return this.request('GET', `/crm/tickets${params}`);
|
|
79
|
+
}
|
|
80
|
+
async getTicket(id) {
|
|
81
|
+
return this.request('GET', `/crm/tickets/${id}`);
|
|
82
|
+
}
|
|
83
|
+
async createTicket(data) {
|
|
84
|
+
return this.request('POST', '/crm/tickets', data);
|
|
85
|
+
}
|
|
86
|
+
async updateTicket(id, data) {
|
|
87
|
+
return this.request('PATCH', `/crm/tickets/${id}`, data);
|
|
88
|
+
}
|
|
89
|
+
// ── CRM: Tasks ──
|
|
90
|
+
async listTasks(filters) {
|
|
91
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
92
|
+
return this.request('GET', `/crm/tasks${params}`);
|
|
93
|
+
}
|
|
94
|
+
async createTask(data) {
|
|
95
|
+
return this.request('POST', '/crm/tasks', data);
|
|
96
|
+
}
|
|
97
|
+
async completeTask(id) {
|
|
98
|
+
return this.request('PATCH', `/crm/tasks/${id}/complete`);
|
|
99
|
+
}
|
|
100
|
+
// ── CRM: Pipelines ──
|
|
101
|
+
async listPipelines() {
|
|
102
|
+
return this.request('GET', '/crm/pipelines');
|
|
103
|
+
}
|
|
104
|
+
// ── Messaging ──
|
|
105
|
+
async sendMessage(to, body, channel = 'whatsapp') {
|
|
106
|
+
return this.request('POST', '/messages', { to, body, channel });
|
|
107
|
+
}
|
|
108
|
+
async sendTemplate(to, templateId, params) {
|
|
109
|
+
return this.request('POST', '/messages/templates', { to, template_id: templateId, params });
|
|
110
|
+
}
|
|
111
|
+
async listConversations(filters) {
|
|
112
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
113
|
+
return this.request('GET', `/conversations${params}`);
|
|
114
|
+
}
|
|
115
|
+
async listMessages(conversationId, limit = 20) {
|
|
116
|
+
return this.request('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
|
|
117
|
+
}
|
|
118
|
+
// ── Voice ──
|
|
119
|
+
async listCalls(filters) {
|
|
120
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
121
|
+
return this.request('GET', `/calls${params}`);
|
|
122
|
+
}
|
|
123
|
+
async getCallTranscript(callId) {
|
|
124
|
+
return this.request('GET', `/calls/${callId}/transcript`);
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mirlo MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes Mirlo CRM, Messaging, and Voice as MCP tools
|
|
6
|
+
* for use with Claude, GPT, and other LLM clients.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @mirlo/mcp --api-key=sk_live_xxx
|
|
10
|
+
* MIRLO_API_KEY=sk_live_xxx npx @mirlo/mcp
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Mirlo MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes Mirlo CRM, Messaging, and Voice as MCP tools
|
|
6
|
+
* for use with Claude, GPT, and other LLM clients.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @mirlo/mcp --api-key=sk_live_xxx
|
|
10
|
+
* MIRLO_API_KEY=sk_live_xxx npx @mirlo/mcp
|
|
11
|
+
*/
|
|
12
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { MirloClient } from './client.js';
|
|
15
|
+
import { ALL_TOOLS } from './tools.js';
|
|
16
|
+
// ── Parse API key ──
|
|
17
|
+
function getApiKey() {
|
|
18
|
+
// From CLI arg: --api-key=xxx
|
|
19
|
+
const cliArg = process.argv.find(a => a.startsWith('--api-key='));
|
|
20
|
+
if (cliArg)
|
|
21
|
+
return cliArg.split('=')[1];
|
|
22
|
+
// From env
|
|
23
|
+
if (process.env.MIRLO_API_KEY)
|
|
24
|
+
return process.env.MIRLO_API_KEY;
|
|
25
|
+
console.error('Error: API key required. Use --api-key=sk_live_xxx or set MIRLO_API_KEY env var.');
|
|
26
|
+
console.error('Get your API key at: app.mirlo.com → Mi Organización → API Keys');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
return ''; // unreachable, satisfies TS
|
|
29
|
+
}
|
|
30
|
+
function getBaseUrl() {
|
|
31
|
+
const cliArg = process.argv.find(a => a.startsWith('--base-url='));
|
|
32
|
+
if (cliArg)
|
|
33
|
+
return cliArg.split('=')[1];
|
|
34
|
+
return process.env.MIRLO_BASE_URL || 'https://api.mirlo.com/v1';
|
|
35
|
+
}
|
|
36
|
+
// ── Main ──
|
|
37
|
+
async function main() {
|
|
38
|
+
const apiKey = getApiKey();
|
|
39
|
+
const baseUrl = getBaseUrl();
|
|
40
|
+
const client = new MirloClient(apiKey, baseUrl);
|
|
41
|
+
const server = new McpServer({
|
|
42
|
+
name: 'mirlo',
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
description: 'Mirlo CRM, Messaging & Voice — manage deals, contacts, tickets, tasks, send messages, and track calls.',
|
|
45
|
+
});
|
|
46
|
+
// Register all tools
|
|
47
|
+
for (const tool of ALL_TOOLS) {
|
|
48
|
+
server.tool(tool.name, tool.description, tool.schema.shape, async (args) => {
|
|
49
|
+
try {
|
|
50
|
+
const result = await tool.handler(client, args);
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: JSON.stringify(result, null, 2),
|
|
55
|
+
}],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{
|
|
61
|
+
type: 'text',
|
|
62
|
+
text: `Error: ${error.message}`,
|
|
63
|
+
}],
|
|
64
|
+
isError: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// Start stdio transport
|
|
70
|
+
const transport = new StdioServerTransport();
|
|
71
|
+
await server.connect(transport);
|
|
72
|
+
}
|
|
73
|
+
main().catch(err => {
|
|
74
|
+
console.error('Fatal:', err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool definitions for Mirlo CRM, Messaging, and Voice.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import type { MirloClient } from './client.js';
|
|
6
|
+
export interface ToolDef {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
schema: z.ZodObject<any>;
|
|
10
|
+
handler: (client: MirloClient, args: any) => Promise<any>;
|
|
11
|
+
}
|
|
12
|
+
export declare const ALL_TOOLS: ToolDef[];
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool definitions for Mirlo CRM, Messaging, and Voice.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
// ── CRM: Deals ──
|
|
6
|
+
const listDeals = {
|
|
7
|
+
name: 'mirlo_list_deals',
|
|
8
|
+
description: 'List deals/opportunities with optional filters. Returns paginated results with total count.',
|
|
9
|
+
schema: z.object({
|
|
10
|
+
search: z.string().optional().describe('Search by deal name'),
|
|
11
|
+
pipeline_id: z.string().optional().describe('Filter by pipeline UUID'),
|
|
12
|
+
stage_id: z.string().optional().describe('Filter by stage UUID'),
|
|
13
|
+
status: z.enum(['open', 'won', 'lost']).optional().describe('Filter by status'),
|
|
14
|
+
owner_id: z.string().optional().describe('Filter by owner member UUID'),
|
|
15
|
+
account_id: z.string().optional().describe('Filter by account UUID'),
|
|
16
|
+
amount_min: z.number().optional().describe('Minimum deal amount'),
|
|
17
|
+
amount_max: z.number().optional().describe('Maximum deal amount'),
|
|
18
|
+
min_stage_hours: z.number().optional().describe('Minimum hours in current stage'),
|
|
19
|
+
max_stage_hours: z.number().optional().describe('Maximum hours in current stage'),
|
|
20
|
+
product_id: z.string().optional().describe('Filter by product UUID'),
|
|
21
|
+
currency: z.string().optional().describe('Filter by currency code (MXN, USD, etc.)'),
|
|
22
|
+
expected_close_before: z.string().optional().describe('Close date before (YYYY-MM-DD) — deals closing before this date'),
|
|
23
|
+
expected_close_after: z.string().optional().describe('Close date after (YYYY-MM-DD) — deals closing after this date'),
|
|
24
|
+
created_after: z.string().optional().describe('Created after (YYYY-MM-DD)'),
|
|
25
|
+
created_before: z.string().optional().describe('Created before (YYYY-MM-DD)'),
|
|
26
|
+
sort_by: z.string().optional().describe('Sort by field: amount, created_at, expected_close_date, name'),
|
|
27
|
+
sort_dir: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
|
|
28
|
+
limit: z.number().optional().default(20).describe('Max results (default 20)'),
|
|
29
|
+
offset: z.number().optional().default(0).describe('Pagination offset'),
|
|
30
|
+
}),
|
|
31
|
+
handler: async (client, args) => client.listDeals(args),
|
|
32
|
+
};
|
|
33
|
+
const createDeal = {
|
|
34
|
+
name: 'mirlo_create_deal',
|
|
35
|
+
description: 'Create a new deal/opportunity in a pipeline.',
|
|
36
|
+
schema: z.object({
|
|
37
|
+
name: z.string().describe('Deal name'),
|
|
38
|
+
pipeline_id: z.string().describe('Pipeline UUID'),
|
|
39
|
+
stage_id: z.string().describe('Initial stage UUID'),
|
|
40
|
+
amount: z.number().optional().describe('Deal amount'),
|
|
41
|
+
currency: z.string().optional().default('MXN').describe('Currency code (default MXN)'),
|
|
42
|
+
owner_id: z.string().optional().describe('Owner member UUID'),
|
|
43
|
+
account_id: z.string().optional().describe('Account UUID'),
|
|
44
|
+
expected_close_date: z.string().optional().describe('Expected close date (YYYY-MM-DD)'),
|
|
45
|
+
}),
|
|
46
|
+
handler: async (client, args) => client.createDeal(args),
|
|
47
|
+
};
|
|
48
|
+
const updateDeal = {
|
|
49
|
+
name: 'mirlo_update_deal',
|
|
50
|
+
description: 'Update an existing deal. Only provided fields are updated.',
|
|
51
|
+
schema: z.object({
|
|
52
|
+
deal_id: z.string().describe('Deal UUID'),
|
|
53
|
+
name: z.string().optional(),
|
|
54
|
+
amount: z.number().optional(),
|
|
55
|
+
owner_id: z.string().optional(),
|
|
56
|
+
account_id: z.string().optional(),
|
|
57
|
+
status: z.enum(['open', 'won', 'lost']).optional(),
|
|
58
|
+
expected_close_date: z.string().optional(),
|
|
59
|
+
}),
|
|
60
|
+
handler: async (client, { deal_id, ...data }) => client.updateDeal(deal_id, data),
|
|
61
|
+
};
|
|
62
|
+
const moveDealStage = {
|
|
63
|
+
name: 'mirlo_move_deal_stage',
|
|
64
|
+
description: 'Move a deal to a different pipeline stage.',
|
|
65
|
+
schema: z.object({
|
|
66
|
+
deal_id: z.string().describe('Deal UUID'),
|
|
67
|
+
stage_id: z.string().describe('Target stage UUID'),
|
|
68
|
+
}),
|
|
69
|
+
handler: async (client, args) => client.moveDealStage(args.deal_id, args.stage_id),
|
|
70
|
+
};
|
|
71
|
+
const getDeal = {
|
|
72
|
+
name: 'mirlo_get_deal',
|
|
73
|
+
description: 'Get a deal by ID with full details including stage times, contacts, and conversations.',
|
|
74
|
+
schema: z.object({
|
|
75
|
+
deal_id: z.string().describe('Deal UUID'),
|
|
76
|
+
}),
|
|
77
|
+
handler: async (client, args) => client.getDeal(args.deal_id),
|
|
78
|
+
};
|
|
79
|
+
// ── CRM: Contacts ──
|
|
80
|
+
const listContacts = {
|
|
81
|
+
name: 'mirlo_list_contacts',
|
|
82
|
+
description: 'List contacts with optional search and filters.',
|
|
83
|
+
schema: z.object({
|
|
84
|
+
search: z.string().optional().describe('Search by name, email, or phone'),
|
|
85
|
+
owner_id: z.string().optional().describe('Filter by owner'),
|
|
86
|
+
account_id: z.string().optional().describe('Filter by account'),
|
|
87
|
+
label_ids: z.string().optional().describe('Comma-separated label UUIDs'),
|
|
88
|
+
lifecycle_stage: z.string().optional().describe('lead, customer, partner, vendor, other'),
|
|
89
|
+
source_address: z.string().optional().describe('Filter by source phone/channel address'),
|
|
90
|
+
limit: z.number().optional().default(20),
|
|
91
|
+
offset: z.number().optional().default(0),
|
|
92
|
+
}),
|
|
93
|
+
handler: async (client, args) => client.listContacts(args),
|
|
94
|
+
};
|
|
95
|
+
const createContact = {
|
|
96
|
+
name: 'mirlo_create_contact',
|
|
97
|
+
description: 'Create a new contact.',
|
|
98
|
+
schema: z.object({
|
|
99
|
+
full_name: z.string().describe('Full name'),
|
|
100
|
+
first_name: z.string().optional(),
|
|
101
|
+
last_name: z.string().optional(),
|
|
102
|
+
primary_email: z.string().optional().describe('Email address'),
|
|
103
|
+
primary_phone: z.string().optional().describe('Phone number'),
|
|
104
|
+
account_id: z.string().optional().describe('Account UUID to link'),
|
|
105
|
+
}),
|
|
106
|
+
handler: async (client, args) => client.createContact(args),
|
|
107
|
+
};
|
|
108
|
+
const getContact = {
|
|
109
|
+
name: 'mirlo_get_contact',
|
|
110
|
+
description: 'Get a contact by ID.',
|
|
111
|
+
schema: z.object({ contact_id: z.string().describe('Contact UUID') }),
|
|
112
|
+
handler: async (client, args) => client.getContact(args.contact_id),
|
|
113
|
+
};
|
|
114
|
+
// ── CRM: Accounts ──
|
|
115
|
+
const listAccounts = {
|
|
116
|
+
name: 'mirlo_list_accounts',
|
|
117
|
+
description: 'List accounts/companies with optional filters.',
|
|
118
|
+
schema: z.object({
|
|
119
|
+
search: z.string().optional().describe('Search by name or domain'),
|
|
120
|
+
industry: z.string().optional(),
|
|
121
|
+
lifecycle_stage: z.string().optional().describe('lead, customer, partner, vendor, other'),
|
|
122
|
+
owner_id: z.string().optional().describe('Filter by owner member UUID'),
|
|
123
|
+
source: z.string().optional().describe('manual, import, api, whatsapp, flow'),
|
|
124
|
+
city: z.string().optional(),
|
|
125
|
+
country: z.string().optional(),
|
|
126
|
+
limit: z.number().optional().default(20),
|
|
127
|
+
offset: z.number().optional().default(0),
|
|
128
|
+
}),
|
|
129
|
+
handler: async (client, args) => client.listAccounts(args),
|
|
130
|
+
};
|
|
131
|
+
const createAccount = {
|
|
132
|
+
name: 'mirlo_create_account',
|
|
133
|
+
description: 'Create a new account/company.',
|
|
134
|
+
schema: z.object({
|
|
135
|
+
name: z.string().describe('Company name'),
|
|
136
|
+
domain: z.string().optional().describe('Company domain'),
|
|
137
|
+
industry: z.string().optional(),
|
|
138
|
+
phone: z.string().optional(),
|
|
139
|
+
email: z.string().optional(),
|
|
140
|
+
city: z.string().optional(),
|
|
141
|
+
country: z.string().optional(),
|
|
142
|
+
}),
|
|
143
|
+
handler: async (client, args) => client.createAccount(args),
|
|
144
|
+
};
|
|
145
|
+
// ── CRM: Tickets ──
|
|
146
|
+
const listTickets = {
|
|
147
|
+
name: 'mirlo_list_tickets',
|
|
148
|
+
description: 'List support tickets with filters.',
|
|
149
|
+
schema: z.object({
|
|
150
|
+
search: z.string().optional().describe('Search by title or reference'),
|
|
151
|
+
state: z.enum(['new', 'waiting_on_agent', 'waiting_on_customer', 'on_hold', 'closed']).optional().describe('Filter by specific state'),
|
|
152
|
+
exclude_closed: z.boolean().optional().describe('Set to true to get only open tickets (excludes closed)'),
|
|
153
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),
|
|
154
|
+
owner_id: z.string().optional().describe('Filter by assignee'),
|
|
155
|
+
project_id: z.string().optional().describe('Filter by project UUID'),
|
|
156
|
+
source: z.enum(['channel', 'api', 'manual', 'form', 'automation']).optional(),
|
|
157
|
+
channel: z.string().optional().describe('whatsapp, voice, email, webchat, sms'),
|
|
158
|
+
account_id: z.string().optional().describe('Filter by account'),
|
|
159
|
+
limit: z.number().optional().default(20),
|
|
160
|
+
offset: z.number().optional().default(0),
|
|
161
|
+
}),
|
|
162
|
+
handler: async (client, args) => client.listTickets(args),
|
|
163
|
+
};
|
|
164
|
+
const createTicket = {
|
|
165
|
+
name: 'mirlo_create_ticket',
|
|
166
|
+
description: 'Create a support ticket.',
|
|
167
|
+
schema: z.object({
|
|
168
|
+
title: z.string().describe('Ticket title'),
|
|
169
|
+
body_html: z.string().optional().describe('Ticket description'),
|
|
170
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal'),
|
|
171
|
+
owner_id: z.string().optional().describe('Assignee member UUID'),
|
|
172
|
+
}),
|
|
173
|
+
handler: async (client, args) => client.createTicket({ ...args, source: 'api' }),
|
|
174
|
+
};
|
|
175
|
+
// ── CRM: Tasks ──
|
|
176
|
+
const listTasks = {
|
|
177
|
+
name: 'mirlo_list_tasks',
|
|
178
|
+
description: 'List tasks with filters. Supports filtering by assignee, status, due date, and associated entities.',
|
|
179
|
+
schema: z.object({
|
|
180
|
+
owner_id: z.string().optional().describe('Filter by assignee'),
|
|
181
|
+
is_completed: z.boolean().optional().describe('true=completed, false=pending'),
|
|
182
|
+
type: z.enum(['to_do', 'call', 'email', 'meeting', 'follow_up']).optional(),
|
|
183
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),
|
|
184
|
+
due_before: z.string().optional().describe('Due date before (YYYY-MM-DD) — tasks due before this date'),
|
|
185
|
+
due_after: z.string().optional().describe('Due date after (YYYY-MM-DD) — tasks due after this date'),
|
|
186
|
+
overdue: z.boolean().optional().describe('Only overdue tasks'),
|
|
187
|
+
deal_id: z.string().optional(),
|
|
188
|
+
ticket_id: z.string().optional(),
|
|
189
|
+
contact_id: z.string().optional(),
|
|
190
|
+
account_id: z.string().optional(),
|
|
191
|
+
search: z.string().optional().describe('Search by title'),
|
|
192
|
+
limit: z.number().optional().default(20),
|
|
193
|
+
}),
|
|
194
|
+
handler: async (client, args) => client.listTasks(args),
|
|
195
|
+
};
|
|
196
|
+
const createTask = {
|
|
197
|
+
name: 'mirlo_create_task',
|
|
198
|
+
description: 'Create a task, optionally associated to a deal, ticket, contact, or account.',
|
|
199
|
+
schema: z.object({
|
|
200
|
+
title: z.string().describe('Task title'),
|
|
201
|
+
type: z.enum(['to_do', 'call', 'email', 'meeting', 'follow_up']).optional().default('to_do'),
|
|
202
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal'),
|
|
203
|
+
due_date: z.string().optional().describe('Due date ISO 8601'),
|
|
204
|
+
owner_id: z.string().optional().describe('Assignee member UUID'),
|
|
205
|
+
deal_id: z.string().optional(),
|
|
206
|
+
ticket_id: z.string().optional(),
|
|
207
|
+
contact_id: z.string().optional(),
|
|
208
|
+
notes: z.string().optional(),
|
|
209
|
+
}),
|
|
210
|
+
handler: async (client, args) => client.createTask(args),
|
|
211
|
+
};
|
|
212
|
+
const completeTask = {
|
|
213
|
+
name: 'mirlo_complete_task',
|
|
214
|
+
description: 'Mark a task as completed.',
|
|
215
|
+
schema: z.object({ task_id: z.string().describe('Task UUID') }),
|
|
216
|
+
handler: async (client, args) => client.completeTask(args.task_id),
|
|
217
|
+
};
|
|
218
|
+
// ── CRM: Pipelines ──
|
|
219
|
+
const listPipelines = {
|
|
220
|
+
name: 'mirlo_list_pipelines',
|
|
221
|
+
description: 'List all pipelines with their stages. Useful for getting pipeline/stage IDs before creating deals.',
|
|
222
|
+
schema: z.object({}),
|
|
223
|
+
handler: async (client) => client.listPipelines(),
|
|
224
|
+
};
|
|
225
|
+
// ── Messaging ──
|
|
226
|
+
const sendMessage = {
|
|
227
|
+
name: 'mirlo_send_message',
|
|
228
|
+
description: 'Send a WhatsApp or SMS message to a phone number.',
|
|
229
|
+
schema: z.object({
|
|
230
|
+
to: z.string().describe('Recipient phone number (E.164 format)'),
|
|
231
|
+
body: z.string().describe('Message text'),
|
|
232
|
+
channel: z.enum(['whatsapp', 'sms']).optional().default('whatsapp'),
|
|
233
|
+
}),
|
|
234
|
+
handler: async (client, args) => client.sendMessage(args.to, args.body, args.channel),
|
|
235
|
+
};
|
|
236
|
+
const sendTemplate = {
|
|
237
|
+
name: 'mirlo_send_template',
|
|
238
|
+
description: 'Send a pre-approved WhatsApp template message.',
|
|
239
|
+
schema: z.object({
|
|
240
|
+
to: z.string().describe('Recipient phone number'),
|
|
241
|
+
template_id: z.string().describe('Template ID'),
|
|
242
|
+
params: z.record(z.string(), z.string()).optional().describe('Template parameters'),
|
|
243
|
+
}),
|
|
244
|
+
handler: async (client, args) => client.sendTemplate(args.to, args.template_id, args.params),
|
|
245
|
+
};
|
|
246
|
+
const listConversations = {
|
|
247
|
+
name: 'mirlo_list_conversations',
|
|
248
|
+
description: 'List recent conversations across channels.',
|
|
249
|
+
schema: z.object({
|
|
250
|
+
status: z.string().optional().describe('Filter by status'),
|
|
251
|
+
limit: z.number().optional().default(20),
|
|
252
|
+
}),
|
|
253
|
+
handler: async (client, args) => client.listConversations(args),
|
|
254
|
+
};
|
|
255
|
+
// ── Voice ──
|
|
256
|
+
const listCalls = {
|
|
257
|
+
name: 'mirlo_list_calls',
|
|
258
|
+
description: 'List phone calls with filters.',
|
|
259
|
+
schema: z.object({
|
|
260
|
+
direction: z.enum(['inbound', 'outbound']).optional(),
|
|
261
|
+
limit: z.number().optional().default(20),
|
|
262
|
+
}),
|
|
263
|
+
handler: async (client, args) => client.listCalls(args),
|
|
264
|
+
};
|
|
265
|
+
const getCallTranscript = {
|
|
266
|
+
name: 'mirlo_get_call_transcript',
|
|
267
|
+
description: 'Get the AI-generated transcript of a phone call.',
|
|
268
|
+
schema: z.object({
|
|
269
|
+
call_id: z.string().describe('Call UUID'),
|
|
270
|
+
}),
|
|
271
|
+
handler: async (client, args) => client.getCallTranscript(args.call_id),
|
|
272
|
+
};
|
|
273
|
+
// ── Export all tools ──
|
|
274
|
+
export const ALL_TOOLS = [
|
|
275
|
+
// CRM
|
|
276
|
+
listDeals, getDeal, createDeal, updateDeal, moveDealStage,
|
|
277
|
+
listContacts, getContact, createContact,
|
|
278
|
+
listAccounts, createAccount,
|
|
279
|
+
listTickets, createTicket,
|
|
280
|
+
listTasks, createTask, completeTask,
|
|
281
|
+
listPipelines,
|
|
282
|
+
// Messaging
|
|
283
|
+
sendMessage, sendTemplate, listConversations,
|
|
284
|
+
// Voice
|
|
285
|
+
listCalls, getCallTranscript,
|
|
286
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mirlo-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Mirlo MCP Server — CRM, Messaging & Voice tools for Claude, GPT, and other LLMs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mirlo-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/index.ts",
|
|
13
|
+
"start": "node dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["mirlo", "mcp", "crm", "whatsapp", "voice", "ai", "claude"],
|
|
16
|
+
"author": "Mirlo <dev@mirlo.mx>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/MirloLLC/mirlo-mcp-server"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
24
|
+
"zod": "^4.4.3"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^26.1.1",
|
|
28
|
+
"tsx": "^4.23.1",
|
|
29
|
+
"typescript": "^7.0.2"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirlo API Client — wraps the public API for CRM, Messaging, and Voice.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class MirloClient {
|
|
6
|
+
private readonly baseUrl: string;
|
|
7
|
+
private readonly apiKey: string;
|
|
8
|
+
|
|
9
|
+
constructor(apiKey: string, baseUrl = 'https://api.mirlo.com/v1') {
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
this.baseUrl = baseUrl;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
15
|
+
const url = `${this.baseUrl}${path}`;
|
|
16
|
+
const res = await fetch(url, {
|
|
17
|
+
method,
|
|
18
|
+
headers: {
|
|
19
|
+
'X-API-Key': this.apiKey,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
throw new Error(`Mirlo API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return res.json() as T;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── CRM: Deals ──
|
|
34
|
+
|
|
35
|
+
async listDeals(filters?: Record<string, string | number | boolean>) {
|
|
36
|
+
const params = filters ? '?' + new URLSearchParams(Object.entries(filters).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
37
|
+
return this.request<any>('GET', `/crm/deals${params}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getDeal(id: string) {
|
|
41
|
+
return this.request<any>('GET', `/crm/deals/${id}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async createDeal(data: Record<string, unknown>) {
|
|
45
|
+
return this.request<any>('POST', '/crm/deals', data);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async updateDeal(id: string, data: Record<string, unknown>) {
|
|
49
|
+
return this.request<any>('PATCH', `/crm/deals/${id}`, data);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async moveDealStage(id: string, stageId: string) {
|
|
53
|
+
return this.request<any>('PATCH', `/crm/deals/${id}/stage`, { stage_id: stageId });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async deleteDeal(id: string) {
|
|
57
|
+
return this.request<any>('DELETE', `/crm/deals/${id}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── CRM: Contacts ──
|
|
61
|
+
|
|
62
|
+
async listContacts(filters?: Record<string, string>) {
|
|
63
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
64
|
+
return this.request<any>('GET', `/crm/contacts${params}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async getContact(id: string) {
|
|
68
|
+
return this.request<any>('GET', `/crm/contacts/${id}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async createContact(data: Record<string, unknown>) {
|
|
72
|
+
return this.request<any>('POST', '/crm/contacts', data);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async updateContact(id: string, data: Record<string, unknown>) {
|
|
76
|
+
return this.request<any>('PATCH', `/crm/contacts/${id}`, data);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── CRM: Accounts ──
|
|
80
|
+
|
|
81
|
+
async listAccounts(filters?: Record<string, string>) {
|
|
82
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
83
|
+
return this.request<any>('GET', `/crm/accounts${params}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async getAccount(id: string) {
|
|
87
|
+
return this.request<any>('GET', `/crm/accounts/${id}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async createAccount(data: Record<string, unknown>) {
|
|
91
|
+
return this.request<any>('POST', '/crm/accounts', data);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async updateAccount(id: string, data: Record<string, unknown>) {
|
|
95
|
+
return this.request<any>('PATCH', `/crm/accounts/${id}`, data);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── CRM: Tickets ──
|
|
99
|
+
|
|
100
|
+
async listTickets(filters?: Record<string, string>) {
|
|
101
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
102
|
+
return this.request<any>('GET', `/crm/tickets${params}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async getTicket(id: string) {
|
|
106
|
+
return this.request<any>('GET', `/crm/tickets/${id}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async createTicket(data: Record<string, unknown>) {
|
|
110
|
+
return this.request<any>('POST', '/crm/tickets', data);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async updateTicket(id: string, data: Record<string, unknown>) {
|
|
114
|
+
return this.request<any>('PATCH', `/crm/tickets/${id}`, data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── CRM: Tasks ──
|
|
118
|
+
|
|
119
|
+
async listTasks(filters?: Record<string, string>) {
|
|
120
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
121
|
+
return this.request<any>('GET', `/crm/tasks${params}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async createTask(data: Record<string, unknown>) {
|
|
125
|
+
return this.request<any>('POST', '/crm/tasks', data);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async completeTask(id: string) {
|
|
129
|
+
return this.request<any>('PATCH', `/crm/tasks/${id}/complete`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── CRM: Pipelines ──
|
|
133
|
+
|
|
134
|
+
async listPipelines() {
|
|
135
|
+
return this.request<any>('GET', '/crm/pipelines');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Messaging ──
|
|
139
|
+
|
|
140
|
+
async sendMessage(to: string, body: string, channel = 'whatsapp') {
|
|
141
|
+
return this.request<any>('POST', '/messages', { to, body, channel });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async sendTemplate(to: string, templateId: string, params?: Record<string, string>) {
|
|
145
|
+
return this.request<any>('POST', '/messages/templates', { to, template_id: templateId, params });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async listConversations(filters?: Record<string, string>) {
|
|
149
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
150
|
+
return this.request<any>('GET', `/conversations${params}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async listMessages(conversationId: string, limit = 20) {
|
|
154
|
+
return this.request<any>('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Voice ──
|
|
158
|
+
|
|
159
|
+
async listCalls(filters?: Record<string, string>) {
|
|
160
|
+
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
161
|
+
return this.request<any>('GET', `/calls${params}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async getCallTranscript(callId: string) {
|
|
165
|
+
return this.request<any>('GET', `/calls/${callId}/transcript`);
|
|
166
|
+
}
|
|
167
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mirlo MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Exposes Mirlo CRM, Messaging, and Voice as MCP tools
|
|
7
|
+
* for use with Claude, GPT, and other LLM clients.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx @mirlo/mcp --api-key=sk_live_xxx
|
|
11
|
+
* MIRLO_API_KEY=sk_live_xxx npx @mirlo/mcp
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
15
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
16
|
+
import { MirloClient } from './client.js';
|
|
17
|
+
import { ALL_TOOLS } from './tools.js';
|
|
18
|
+
|
|
19
|
+
// ── Parse API key ──
|
|
20
|
+
|
|
21
|
+
function getApiKey(): string {
|
|
22
|
+
// From CLI arg: --api-key=xxx
|
|
23
|
+
const cliArg = process.argv.find(a => a.startsWith('--api-key='));
|
|
24
|
+
if (cliArg) return cliArg.split('=')[1];
|
|
25
|
+
|
|
26
|
+
// From env
|
|
27
|
+
if (process.env.MIRLO_API_KEY) return process.env.MIRLO_API_KEY;
|
|
28
|
+
|
|
29
|
+
console.error('Error: API key required. Use --api-key=sk_live_xxx or set MIRLO_API_KEY env var.');
|
|
30
|
+
console.error('Get your API key at: app.mirlo.com → Mi Organización → API Keys');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
return ''; // unreachable, satisfies TS
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getBaseUrl(): string {
|
|
36
|
+
const cliArg = process.argv.find(a => a.startsWith('--base-url='));
|
|
37
|
+
if (cliArg) return cliArg.split('=')[1];
|
|
38
|
+
return process.env.MIRLO_BASE_URL || 'https://api.mirlo.com/v1';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Main ──
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const apiKey = getApiKey();
|
|
45
|
+
const baseUrl = getBaseUrl();
|
|
46
|
+
const client = new MirloClient(apiKey, baseUrl);
|
|
47
|
+
|
|
48
|
+
const server = new McpServer({
|
|
49
|
+
name: 'mirlo',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
description: 'Mirlo CRM, Messaging & Voice — manage deals, contacts, tickets, tasks, send messages, and track calls.',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Register all tools
|
|
55
|
+
for (const tool of ALL_TOOLS) {
|
|
56
|
+
server.tool(
|
|
57
|
+
tool.name,
|
|
58
|
+
tool.description,
|
|
59
|
+
tool.schema.shape,
|
|
60
|
+
async (args: any) => {
|
|
61
|
+
try {
|
|
62
|
+
const result = await tool.handler(client, args);
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: 'text' as const,
|
|
66
|
+
text: JSON.stringify(result, null, 2),
|
|
67
|
+
}],
|
|
68
|
+
};
|
|
69
|
+
} catch (error: any) {
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: 'text' as const,
|
|
73
|
+
text: `Error: ${error.message}`,
|
|
74
|
+
}],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Start stdio transport
|
|
83
|
+
const transport = new StdioServerTransport();
|
|
84
|
+
await server.connect(transport);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
main().catch(err => {
|
|
88
|
+
console.error('Fatal:', err);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
});
|
package/src/tools.ts
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool definitions for Mirlo CRM, Messaging, and Voice.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import type { MirloClient } from './client.js';
|
|
7
|
+
|
|
8
|
+
// ── Tool definition type ──
|
|
9
|
+
|
|
10
|
+
export interface ToolDef {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
schema: z.ZodObject<any>;
|
|
14
|
+
handler: (client: MirloClient, args: any) => Promise<any>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ── CRM: Deals ──
|
|
18
|
+
|
|
19
|
+
const listDeals: ToolDef = {
|
|
20
|
+
name: 'mirlo_list_deals',
|
|
21
|
+
description: 'List deals/opportunities with optional filters. Returns paginated results with total count.',
|
|
22
|
+
schema: z.object({
|
|
23
|
+
search: z.string().optional().describe('Search by deal name'),
|
|
24
|
+
pipeline_id: z.string().optional().describe('Filter by pipeline UUID'),
|
|
25
|
+
stage_id: z.string().optional().describe('Filter by stage UUID'),
|
|
26
|
+
status: z.enum(['open', 'won', 'lost']).optional().describe('Filter by status'),
|
|
27
|
+
owner_id: z.string().optional().describe('Filter by owner member UUID'),
|
|
28
|
+
account_id: z.string().optional().describe('Filter by account UUID'),
|
|
29
|
+
amount_min: z.number().optional().describe('Minimum deal amount'),
|
|
30
|
+
amount_max: z.number().optional().describe('Maximum deal amount'),
|
|
31
|
+
min_stage_hours: z.number().optional().describe('Minimum hours in current stage'),
|
|
32
|
+
max_stage_hours: z.number().optional().describe('Maximum hours in current stage'),
|
|
33
|
+
product_id: z.string().optional().describe('Filter by product UUID'),
|
|
34
|
+
currency: z.string().optional().describe('Filter by currency code (MXN, USD, etc.)'),
|
|
35
|
+
expected_close_before: z.string().optional().describe('Close date before (YYYY-MM-DD) — deals closing before this date'),
|
|
36
|
+
expected_close_after: z.string().optional().describe('Close date after (YYYY-MM-DD) — deals closing after this date'),
|
|
37
|
+
created_after: z.string().optional().describe('Created after (YYYY-MM-DD)'),
|
|
38
|
+
created_before: z.string().optional().describe('Created before (YYYY-MM-DD)'),
|
|
39
|
+
sort_by: z.string().optional().describe('Sort by field: amount, created_at, expected_close_date, name'),
|
|
40
|
+
sort_dir: z.enum(['asc', 'desc']).optional().describe('Sort direction'),
|
|
41
|
+
limit: z.number().optional().default(20).describe('Max results (default 20)'),
|
|
42
|
+
offset: z.number().optional().default(0).describe('Pagination offset'),
|
|
43
|
+
}),
|
|
44
|
+
handler: async (client, args) => client.listDeals(args),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const createDeal: ToolDef = {
|
|
48
|
+
name: 'mirlo_create_deal',
|
|
49
|
+
description: 'Create a new deal/opportunity in a pipeline.',
|
|
50
|
+
schema: z.object({
|
|
51
|
+
name: z.string().describe('Deal name'),
|
|
52
|
+
pipeline_id: z.string().describe('Pipeline UUID'),
|
|
53
|
+
stage_id: z.string().describe('Initial stage UUID'),
|
|
54
|
+
amount: z.number().optional().describe('Deal amount'),
|
|
55
|
+
currency: z.string().optional().default('MXN').describe('Currency code (default MXN)'),
|
|
56
|
+
owner_id: z.string().optional().describe('Owner member UUID'),
|
|
57
|
+
account_id: z.string().optional().describe('Account UUID'),
|
|
58
|
+
expected_close_date: z.string().optional().describe('Expected close date (YYYY-MM-DD)'),
|
|
59
|
+
}),
|
|
60
|
+
handler: async (client, args) => client.createDeal(args),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const updateDeal: ToolDef = {
|
|
64
|
+
name: 'mirlo_update_deal',
|
|
65
|
+
description: 'Update an existing deal. Only provided fields are updated.',
|
|
66
|
+
schema: z.object({
|
|
67
|
+
deal_id: z.string().describe('Deal UUID'),
|
|
68
|
+
name: z.string().optional(),
|
|
69
|
+
amount: z.number().optional(),
|
|
70
|
+
owner_id: z.string().optional(),
|
|
71
|
+
account_id: z.string().optional(),
|
|
72
|
+
status: z.enum(['open', 'won', 'lost']).optional(),
|
|
73
|
+
expected_close_date: z.string().optional(),
|
|
74
|
+
}),
|
|
75
|
+
handler: async (client, { deal_id, ...data }) => client.updateDeal(deal_id, data),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const moveDealStage: ToolDef = {
|
|
79
|
+
name: 'mirlo_move_deal_stage',
|
|
80
|
+
description: 'Move a deal to a different pipeline stage.',
|
|
81
|
+
schema: z.object({
|
|
82
|
+
deal_id: z.string().describe('Deal UUID'),
|
|
83
|
+
stage_id: z.string().describe('Target stage UUID'),
|
|
84
|
+
}),
|
|
85
|
+
handler: async (client, args) => client.moveDealStage(args.deal_id, args.stage_id),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getDeal: ToolDef = {
|
|
89
|
+
name: 'mirlo_get_deal',
|
|
90
|
+
description: 'Get a deal by ID with full details including stage times, contacts, and conversations.',
|
|
91
|
+
schema: z.object({
|
|
92
|
+
deal_id: z.string().describe('Deal UUID'),
|
|
93
|
+
}),
|
|
94
|
+
handler: async (client, args) => client.getDeal(args.deal_id),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// ── CRM: Contacts ──
|
|
98
|
+
|
|
99
|
+
const listContacts: ToolDef = {
|
|
100
|
+
name: 'mirlo_list_contacts',
|
|
101
|
+
description: 'List contacts with optional search and filters.',
|
|
102
|
+
schema: z.object({
|
|
103
|
+
search: z.string().optional().describe('Search by name, email, or phone'),
|
|
104
|
+
owner_id: z.string().optional().describe('Filter by owner'),
|
|
105
|
+
account_id: z.string().optional().describe('Filter by account'),
|
|
106
|
+
label_ids: z.string().optional().describe('Comma-separated label UUIDs'),
|
|
107
|
+
lifecycle_stage: z.string().optional().describe('lead, customer, partner, vendor, other'),
|
|
108
|
+
source_address: z.string().optional().describe('Filter by source phone/channel address'),
|
|
109
|
+
limit: z.number().optional().default(20),
|
|
110
|
+
offset: z.number().optional().default(0),
|
|
111
|
+
}),
|
|
112
|
+
handler: async (client, args) => client.listContacts(args),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const createContact: ToolDef = {
|
|
116
|
+
name: 'mirlo_create_contact',
|
|
117
|
+
description: 'Create a new contact.',
|
|
118
|
+
schema: z.object({
|
|
119
|
+
full_name: z.string().describe('Full name'),
|
|
120
|
+
first_name: z.string().optional(),
|
|
121
|
+
last_name: z.string().optional(),
|
|
122
|
+
primary_email: z.string().optional().describe('Email address'),
|
|
123
|
+
primary_phone: z.string().optional().describe('Phone number'),
|
|
124
|
+
account_id: z.string().optional().describe('Account UUID to link'),
|
|
125
|
+
}),
|
|
126
|
+
handler: async (client, args) => client.createContact(args),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const getContact: ToolDef = {
|
|
130
|
+
name: 'mirlo_get_contact',
|
|
131
|
+
description: 'Get a contact by ID.',
|
|
132
|
+
schema: z.object({ contact_id: z.string().describe('Contact UUID') }),
|
|
133
|
+
handler: async (client, args) => client.getContact(args.contact_id),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// ── CRM: Accounts ──
|
|
137
|
+
|
|
138
|
+
const listAccounts: ToolDef = {
|
|
139
|
+
name: 'mirlo_list_accounts',
|
|
140
|
+
description: 'List accounts/companies with optional filters.',
|
|
141
|
+
schema: z.object({
|
|
142
|
+
search: z.string().optional().describe('Search by name or domain'),
|
|
143
|
+
industry: z.string().optional(),
|
|
144
|
+
lifecycle_stage: z.string().optional().describe('lead, customer, partner, vendor, other'),
|
|
145
|
+
owner_id: z.string().optional().describe('Filter by owner member UUID'),
|
|
146
|
+
source: z.string().optional().describe('manual, import, api, whatsapp, flow'),
|
|
147
|
+
city: z.string().optional(),
|
|
148
|
+
country: z.string().optional(),
|
|
149
|
+
limit: z.number().optional().default(20),
|
|
150
|
+
offset: z.number().optional().default(0),
|
|
151
|
+
}),
|
|
152
|
+
handler: async (client, args) => client.listAccounts(args),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const createAccount: ToolDef = {
|
|
156
|
+
name: 'mirlo_create_account',
|
|
157
|
+
description: 'Create a new account/company.',
|
|
158
|
+
schema: z.object({
|
|
159
|
+
name: z.string().describe('Company name'),
|
|
160
|
+
domain: z.string().optional().describe('Company domain'),
|
|
161
|
+
industry: z.string().optional(),
|
|
162
|
+
phone: z.string().optional(),
|
|
163
|
+
email: z.string().optional(),
|
|
164
|
+
city: z.string().optional(),
|
|
165
|
+
country: z.string().optional(),
|
|
166
|
+
}),
|
|
167
|
+
handler: async (client, args) => client.createAccount(args),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// ── CRM: Tickets ──
|
|
171
|
+
|
|
172
|
+
const listTickets: ToolDef = {
|
|
173
|
+
name: 'mirlo_list_tickets',
|
|
174
|
+
description: 'List support tickets with filters.',
|
|
175
|
+
schema: z.object({
|
|
176
|
+
search: z.string().optional().describe('Search by title or reference'),
|
|
177
|
+
state: z.enum(['new', 'waiting_on_agent', 'waiting_on_customer', 'on_hold', 'closed']).optional().describe('Filter by specific state'),
|
|
178
|
+
exclude_closed: z.boolean().optional().describe('Set to true to get only open tickets (excludes closed)'),
|
|
179
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),
|
|
180
|
+
owner_id: z.string().optional().describe('Filter by assignee'),
|
|
181
|
+
project_id: z.string().optional().describe('Filter by project UUID'),
|
|
182
|
+
source: z.enum(['channel', 'api', 'manual', 'form', 'automation']).optional(),
|
|
183
|
+
channel: z.string().optional().describe('whatsapp, voice, email, webchat, sms'),
|
|
184
|
+
account_id: z.string().optional().describe('Filter by account'),
|
|
185
|
+
limit: z.number().optional().default(20),
|
|
186
|
+
offset: z.number().optional().default(0),
|
|
187
|
+
}),
|
|
188
|
+
handler: async (client, args) => client.listTickets(args),
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const createTicket: ToolDef = {
|
|
192
|
+
name: 'mirlo_create_ticket',
|
|
193
|
+
description: 'Create a support ticket.',
|
|
194
|
+
schema: z.object({
|
|
195
|
+
title: z.string().describe('Ticket title'),
|
|
196
|
+
body_html: z.string().optional().describe('Ticket description'),
|
|
197
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal'),
|
|
198
|
+
owner_id: z.string().optional().describe('Assignee member UUID'),
|
|
199
|
+
}),
|
|
200
|
+
handler: async (client, args) => client.createTicket({ ...args, source: 'api' }),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// ── CRM: Tasks ──
|
|
204
|
+
|
|
205
|
+
const listTasks: ToolDef = {
|
|
206
|
+
name: 'mirlo_list_tasks',
|
|
207
|
+
description: 'List tasks with filters. Supports filtering by assignee, status, due date, and associated entities.',
|
|
208
|
+
schema: z.object({
|
|
209
|
+
owner_id: z.string().optional().describe('Filter by assignee'),
|
|
210
|
+
is_completed: z.boolean().optional().describe('true=completed, false=pending'),
|
|
211
|
+
type: z.enum(['to_do', 'call', 'email', 'meeting', 'follow_up']).optional(),
|
|
212
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional(),
|
|
213
|
+
due_before: z.string().optional().describe('Due date before (YYYY-MM-DD) — tasks due before this date'),
|
|
214
|
+
due_after: z.string().optional().describe('Due date after (YYYY-MM-DD) — tasks due after this date'),
|
|
215
|
+
overdue: z.boolean().optional().describe('Only overdue tasks'),
|
|
216
|
+
deal_id: z.string().optional(),
|
|
217
|
+
ticket_id: z.string().optional(),
|
|
218
|
+
contact_id: z.string().optional(),
|
|
219
|
+
account_id: z.string().optional(),
|
|
220
|
+
search: z.string().optional().describe('Search by title'),
|
|
221
|
+
limit: z.number().optional().default(20),
|
|
222
|
+
}),
|
|
223
|
+
handler: async (client, args) => client.listTasks(args),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const createTask: ToolDef = {
|
|
227
|
+
name: 'mirlo_create_task',
|
|
228
|
+
description: 'Create a task, optionally associated to a deal, ticket, contact, or account.',
|
|
229
|
+
schema: z.object({
|
|
230
|
+
title: z.string().describe('Task title'),
|
|
231
|
+
type: z.enum(['to_do', 'call', 'email', 'meeting', 'follow_up']).optional().default('to_do'),
|
|
232
|
+
priority: z.enum(['low', 'normal', 'high', 'urgent']).optional().default('normal'),
|
|
233
|
+
due_date: z.string().optional().describe('Due date ISO 8601'),
|
|
234
|
+
owner_id: z.string().optional().describe('Assignee member UUID'),
|
|
235
|
+
deal_id: z.string().optional(),
|
|
236
|
+
ticket_id: z.string().optional(),
|
|
237
|
+
contact_id: z.string().optional(),
|
|
238
|
+
notes: z.string().optional(),
|
|
239
|
+
}),
|
|
240
|
+
handler: async (client, args) => client.createTask(args),
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const completeTask: ToolDef = {
|
|
244
|
+
name: 'mirlo_complete_task',
|
|
245
|
+
description: 'Mark a task as completed.',
|
|
246
|
+
schema: z.object({ task_id: z.string().describe('Task UUID') }),
|
|
247
|
+
handler: async (client, args) => client.completeTask(args.task_id),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// ── CRM: Pipelines ──
|
|
251
|
+
|
|
252
|
+
const listPipelines: ToolDef = {
|
|
253
|
+
name: 'mirlo_list_pipelines',
|
|
254
|
+
description: 'List all pipelines with their stages. Useful for getting pipeline/stage IDs before creating deals.',
|
|
255
|
+
schema: z.object({}),
|
|
256
|
+
handler: async (client) => client.listPipelines(),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// ── Messaging ──
|
|
260
|
+
|
|
261
|
+
const sendMessage: ToolDef = {
|
|
262
|
+
name: 'mirlo_send_message',
|
|
263
|
+
description: 'Send a WhatsApp or SMS message to a phone number.',
|
|
264
|
+
schema: z.object({
|
|
265
|
+
to: z.string().describe('Recipient phone number (E.164 format)'),
|
|
266
|
+
body: z.string().describe('Message text'),
|
|
267
|
+
channel: z.enum(['whatsapp', 'sms']).optional().default('whatsapp'),
|
|
268
|
+
}),
|
|
269
|
+
handler: async (client, args) => client.sendMessage(args.to, args.body, args.channel),
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const sendTemplate: ToolDef = {
|
|
273
|
+
name: 'mirlo_send_template',
|
|
274
|
+
description: 'Send a pre-approved WhatsApp template message.',
|
|
275
|
+
schema: z.object({
|
|
276
|
+
to: z.string().describe('Recipient phone number'),
|
|
277
|
+
template_id: z.string().describe('Template ID'),
|
|
278
|
+
params: z.record(z.string(), z.string()).optional().describe('Template parameters'),
|
|
279
|
+
}),
|
|
280
|
+
handler: async (client, args) => client.sendTemplate(args.to, args.template_id, args.params),
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const listConversations: ToolDef = {
|
|
284
|
+
name: 'mirlo_list_conversations',
|
|
285
|
+
description: 'List recent conversations across channels.',
|
|
286
|
+
schema: z.object({
|
|
287
|
+
status: z.string().optional().describe('Filter by status'),
|
|
288
|
+
limit: z.number().optional().default(20),
|
|
289
|
+
}),
|
|
290
|
+
handler: async (client, args) => client.listConversations(args),
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// ── Voice ──
|
|
294
|
+
|
|
295
|
+
const listCalls: ToolDef = {
|
|
296
|
+
name: 'mirlo_list_calls',
|
|
297
|
+
description: 'List phone calls with filters.',
|
|
298
|
+
schema: z.object({
|
|
299
|
+
direction: z.enum(['inbound', 'outbound']).optional(),
|
|
300
|
+
limit: z.number().optional().default(20),
|
|
301
|
+
}),
|
|
302
|
+
handler: async (client, args) => client.listCalls(args as any),
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const getCallTranscript: ToolDef = {
|
|
306
|
+
name: 'mirlo_get_call_transcript',
|
|
307
|
+
description: 'Get the AI-generated transcript of a phone call.',
|
|
308
|
+
schema: z.object({
|
|
309
|
+
call_id: z.string().describe('Call UUID'),
|
|
310
|
+
}),
|
|
311
|
+
handler: async (client, args) => client.getCallTranscript(args.call_id),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// ── Export all tools ──
|
|
315
|
+
|
|
316
|
+
export const ALL_TOOLS: ToolDef[] = [
|
|
317
|
+
// CRM
|
|
318
|
+
listDeals, getDeal, createDeal, updateDeal, moveDealStage,
|
|
319
|
+
listContacts, getContact, createContact,
|
|
320
|
+
listAccounts, createAccount,
|
|
321
|
+
listTickets, createTicket,
|
|
322
|
+
listTasks, createTask, completeTask,
|
|
323
|
+
listPipelines,
|
|
324
|
+
// Messaging
|
|
325
|
+
sendMessage, sendTemplate, listConversations,
|
|
326
|
+
// Voice
|
|
327
|
+
listCalls, getCallTranscript,
|
|
328
|
+
];
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|