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 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
@@ -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
+ }
@@ -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
+ });
@@ -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
+ }