mirlo-mcp 1.0.2 → 1.0.4

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/dist/client.d.ts CHANGED
@@ -4,7 +4,9 @@
4
4
  export declare class MirloClient {
5
5
  private readonly baseUrl;
6
6
  private readonly apiKey;
7
+ private orgId;
7
8
  constructor(apiKey: string, baseUrl?: string);
9
+ private getMessagesBaseUrl;
8
10
  private request;
9
11
  listDeals(filters?: Record<string, string | number | boolean>): Promise<any>;
10
12
  getDeal(id: string): Promise<any>;
@@ -29,8 +31,10 @@ export declare class MirloClient {
29
31
  createTask(data: Record<string, unknown>): Promise<any>;
30
32
  completeTask(id: string): Promise<any>;
31
33
  listPipelines(): Promise<any>;
32
- sendMessage(to: string, body: string, channel?: string): Promise<any>;
33
- sendTemplate(to: string, templateId: string, params?: Record<string, string>): Promise<any>;
34
+ private resolveOrgId;
35
+ private messagesRequest;
36
+ sendMessage(organizationAddress: string, contactAddress: string, text: string, service?: string): Promise<any>;
37
+ sendTemplate(organizationAddress: string, to: string, metaTemplateId: string, components?: any[]): Promise<any>;
34
38
  listConversations(filters?: Record<string, string>): Promise<any>;
35
39
  listMessages(conversationId: string, limit?: number): Promise<any>;
36
40
  listCalls(filters?: Record<string, string>): Promise<any>;
package/dist/client.js CHANGED
@@ -4,10 +4,15 @@
4
4
  export class MirloClient {
5
5
  baseUrl;
6
6
  apiKey;
7
+ orgId = null;
7
8
  constructor(apiKey, baseUrl = 'https://api.mirlo.com/v1') {
8
9
  this.apiKey = apiKey;
9
10
  this.baseUrl = baseUrl;
10
11
  }
12
+ getMessagesBaseUrl() {
13
+ // Messages API is v2, not v1
14
+ return this.baseUrl.replace('/v1', '/v2/messages');
15
+ }
11
16
  async request(method, path, body) {
12
17
  const url = `${this.baseUrl}${path}`;
13
18
  const res = await fetch(url, {
@@ -105,18 +110,66 @@ export class MirloClient {
105
110
  return this.request('GET', '/crm/pipelines');
106
111
  }
107
112
  // ── Messaging ──
108
- async sendMessage(to, body, channel = 'whatsapp') {
109
- return this.request('POST', '/messages', { to, body, channel });
113
+ // Messages API is v2 at /v2/messages/organizations/{orgId}/...
114
+ async resolveOrgId() {
115
+ if (this.orgId)
116
+ return this.orgId;
117
+ try {
118
+ const res = await this.listAccounts({ limit: '1' });
119
+ if (res?.data?.[0]?.organization_id) {
120
+ this.orgId = res.data[0].organization_id;
121
+ return this.orgId;
122
+ }
123
+ }
124
+ catch { }
125
+ try {
126
+ const res = await this.listContacts({ limit: '1' });
127
+ if (res?.data?.[0]?.organization_id) {
128
+ this.orgId = res.data[0].organization_id;
129
+ return this.orgId;
130
+ }
131
+ }
132
+ catch { }
133
+ throw new Error('Could not resolve organization_id. Make sure your API key has CRM data.');
110
134
  }
111
- async sendTemplate(to, templateId, params) {
112
- return this.request('POST', '/messages/templates', { to, template_id: templateId, params });
135
+ async messagesRequest(method, path, body) {
136
+ const orgId = await this.resolveOrgId();
137
+ const msgBase = this.getMessagesBaseUrl();
138
+ const url = `${msgBase}/organizations/${orgId}${path}`;
139
+ const res = await fetch(url, {
140
+ method,
141
+ headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
142
+ ...(body ? { body: JSON.stringify(body) } : {}),
143
+ });
144
+ if (!res.ok) {
145
+ const text = await res.text();
146
+ throw new Error(`Mirlo Messages API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
147
+ }
148
+ return res.json();
149
+ }
150
+ async sendMessage(organizationAddress, contactAddress, text, service = 'whatsapp') {
151
+ return this.messagesRequest('POST', '/messages', {
152
+ organization_address: organizationAddress,
153
+ service,
154
+ contact_address: contactAddress,
155
+ direction: 'outgoing',
156
+ content: { kind: 'text', text },
157
+ });
158
+ }
159
+ async sendTemplate(organizationAddress, to, metaTemplateId, components) {
160
+ return this.messagesRequest('POST', '/messages/send-template', {
161
+ organization_address: organizationAddress,
162
+ to,
163
+ meta_template_id: metaTemplateId,
164
+ components,
165
+ });
113
166
  }
114
167
  async listConversations(filters) {
115
168
  const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
116
- return this.request('GET', `/conversations${params}`);
169
+ return this.messagesRequest('GET', `/conversations${params}`);
117
170
  }
118
171
  async listMessages(conversationId, limit = 20) {
119
- return this.request('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
172
+ return this.messagesRequest('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
120
173
  }
121
174
  // ── Voice ──
122
175
  async listCalls(filters) {
package/dist/tools.js CHANGED
@@ -242,23 +242,25 @@ const listPipelines = {
242
242
  // ── Messaging ──
243
243
  const sendMessage = {
244
244
  name: 'mirlo_send_message',
245
- description: 'Send a WhatsApp or SMS message to a phone number.',
245
+ description: 'Send a WhatsApp message to a phone number. Only works within the 24h window after the contact messaged you. Use send_template for first contact.',
246
246
  schema: z.object({
247
- to: z.string().describe('Recipient phone number (E.164 format)'),
248
- body: z.string().describe('Message text'),
249
- channel: z.enum(['whatsapp', 'sms']).optional().default('whatsapp'),
247
+ organization_address: z.string().describe('Your WhatsApp Phone Number ID (from Mirlo dashboard)'),
248
+ contact_address: z.string().describe('Recipient phone number WITHOUT + prefix (e.g. 5215512345678)'),
249
+ text: z.string().describe('Message text'),
250
+ service: z.enum(['whatsapp', 'instagram', 'messenger']).optional().default('whatsapp'),
250
251
  }),
251
- handler: async (client, args) => client.sendMessage(args.to, args.body, args.channel),
252
+ handler: async (client, args) => client.sendMessage(args.organization_address, args.contact_address, args.text, args.service),
252
253
  };
253
254
  const sendTemplate = {
254
255
  name: 'mirlo_send_template',
255
- description: 'Send a pre-approved WhatsApp template message.',
256
+ description: 'Send a pre-approved WhatsApp template message. Works outside the 24h window.',
256
257
  schema: z.object({
257
- to: z.string().describe('Recipient phone number'),
258
- template_id: z.string().describe('Template ID'),
259
- params: z.record(z.string(), z.string()).optional().describe('Template parameters'),
258
+ organization_address: z.string().describe('Your WhatsApp Phone Number ID'),
259
+ to: z.string().describe('Recipient phone number with + prefix (e.g. +525512345678)'),
260
+ meta_template_id: z.string().describe('Meta-approved template ID'),
261
+ components: z.any().optional().describe('Template components array with parameters'),
260
262
  }),
261
- handler: async (client, args) => client.sendTemplate(args.to, args.template_id, args.params),
263
+ handler: async (client, args) => client.sendTemplate(args.organization_address, args.to, args.meta_template_id, args.components),
262
264
  };
263
265
  const listConversations = {
264
266
  name: 'mirlo_list_conversations',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirlo-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Mirlo MCP Server — CRM, Messaging & Voice tools for Claude, GPT, and other LLMs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/client.ts CHANGED
@@ -5,12 +5,18 @@
5
5
  export class MirloClient {
6
6
  private readonly baseUrl: string;
7
7
  private readonly apiKey: string;
8
+ private orgId: string | null = null;
8
9
 
9
10
  constructor(apiKey: string, baseUrl = 'https://api.mirlo.com/v1') {
10
11
  this.apiKey = apiKey;
11
12
  this.baseUrl = baseUrl;
12
13
  }
13
14
 
15
+ private getMessagesBaseUrl(): string {
16
+ // Messages API is v2, not v1
17
+ return this.baseUrl.replace('/v1', '/v2/messages');
18
+ }
19
+
14
20
  private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
15
21
  const url = `${this.baseUrl}${path}`;
16
22
  const res = await fetch(url, {
@@ -140,22 +146,69 @@ export class MirloClient {
140
146
  }
141
147
 
142
148
  // ── Messaging ──
149
+ // Messages API is v2 at /v2/messages/organizations/{orgId}/...
150
+
151
+ private async resolveOrgId(): Promise<string> {
152
+ if (this.orgId) return this.orgId;
153
+ try {
154
+ const res = await this.listAccounts({ limit: '1' });
155
+ if (res?.data?.[0]?.organization_id) {
156
+ this.orgId = res.data[0].organization_id;
157
+ return this.orgId!;
158
+ }
159
+ } catch {}
160
+ try {
161
+ const res = await this.listContacts({ limit: '1' });
162
+ if (res?.data?.[0]?.organization_id) {
163
+ this.orgId = res.data[0].organization_id;
164
+ return this.orgId!;
165
+ }
166
+ } catch {}
167
+ throw new Error('Could not resolve organization_id. Make sure your API key has CRM data.');
168
+ }
169
+
170
+ private async messagesRequest<T>(method: string, path: string, body?: unknown): Promise<T> {
171
+ const orgId = await this.resolveOrgId();
172
+ const msgBase = this.getMessagesBaseUrl();
173
+ const url = `${msgBase}/organizations/${orgId}${path}`;
174
+ const res = await fetch(url, {
175
+ method,
176
+ headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
177
+ ...(body ? { body: JSON.stringify(body) } : {}),
178
+ });
179
+ if (!res.ok) {
180
+ const text = await res.text();
181
+ throw new Error(`Mirlo Messages API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
182
+ }
183
+ return res.json() as T;
184
+ }
143
185
 
144
- async sendMessage(to: string, body: string, channel = 'whatsapp') {
145
- return this.request<any>('POST', '/messages', { to, body, channel });
186
+ async sendMessage(organizationAddress: string, contactAddress: string, text: string, service = 'whatsapp') {
187
+ return this.messagesRequest<any>('POST', '/messages', {
188
+ organization_address: organizationAddress,
189
+ service,
190
+ contact_address: contactAddress,
191
+ direction: 'outgoing',
192
+ content: { kind: 'text', text },
193
+ });
146
194
  }
147
195
 
148
- async sendTemplate(to: string, templateId: string, params?: Record<string, string>) {
149
- return this.request<any>('POST', '/messages/templates', { to, template_id: templateId, params });
196
+ async sendTemplate(organizationAddress: string, to: string, metaTemplateId: string, components?: any[]) {
197
+ return this.messagesRequest<any>('POST', '/messages/send-template', {
198
+ organization_address: organizationAddress,
199
+ to,
200
+ meta_template_id: metaTemplateId,
201
+ components,
202
+ });
150
203
  }
151
204
 
152
205
  async listConversations(filters?: Record<string, string>) {
153
206
  const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
154
- return this.request<any>('GET', `/conversations${params}`);
207
+ return this.messagesRequest<any>('GET', `/conversations${params}`);
155
208
  }
156
209
 
157
210
  async listMessages(conversationId: string, limit = 20) {
158
- return this.request<any>('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
211
+ return this.messagesRequest<any>('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
159
212
  }
160
213
 
161
214
  // ── Voice ──
package/src/tools.ts CHANGED
@@ -278,24 +278,26 @@ const listPipelines: ToolDef = {
278
278
 
279
279
  const sendMessage: ToolDef = {
280
280
  name: 'mirlo_send_message',
281
- description: 'Send a WhatsApp or SMS message to a phone number.',
281
+ description: 'Send a WhatsApp message to a phone number. Only works within the 24h window after the contact messaged you. Use send_template for first contact.',
282
282
  schema: z.object({
283
- to: z.string().describe('Recipient phone number (E.164 format)'),
284
- body: z.string().describe('Message text'),
285
- channel: z.enum(['whatsapp', 'sms']).optional().default('whatsapp'),
283
+ organization_address: z.string().describe('Your WhatsApp Phone Number ID (from Mirlo dashboard)'),
284
+ contact_address: z.string().describe('Recipient phone number WITHOUT + prefix (e.g. 5215512345678)'),
285
+ text: z.string().describe('Message text'),
286
+ service: z.enum(['whatsapp', 'instagram', 'messenger']).optional().default('whatsapp'),
286
287
  }),
287
- handler: async (client, args) => client.sendMessage(args.to, args.body, args.channel),
288
+ handler: async (client, args) => client.sendMessage(args.organization_address, args.contact_address, args.text, args.service),
288
289
  };
289
290
 
290
291
  const sendTemplate: ToolDef = {
291
292
  name: 'mirlo_send_template',
292
- description: 'Send a pre-approved WhatsApp template message.',
293
+ description: 'Send a pre-approved WhatsApp template message. Works outside the 24h window.',
293
294
  schema: z.object({
294
- to: z.string().describe('Recipient phone number'),
295
- template_id: z.string().describe('Template ID'),
296
- params: z.record(z.string(), z.string()).optional().describe('Template parameters'),
295
+ organization_address: z.string().describe('Your WhatsApp Phone Number ID'),
296
+ to: z.string().describe('Recipient phone number with + prefix (e.g. +525512345678)'),
297
+ meta_template_id: z.string().describe('Meta-approved template ID'),
298
+ components: z.any().optional().describe('Template components array with parameters'),
297
299
  }),
298
- handler: async (client, args) => client.sendTemplate(args.to, args.template_id, args.params),
300
+ handler: async (client, args) => client.sendTemplate(args.organization_address, args.to, args.meta_template_id, args.components),
299
301
  };
300
302
 
301
303
  const listConversations: ToolDef = {