mirlo-mcp 1.0.3 → 1.1.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/dist/client.d.ts +5 -1
- package/dist/client.js +87 -31
- package/dist/index.js +8 -1
- package/package.json +1 -1
- package/src/client.ts +87 -31
- package/src/index.ts +8 -1
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
|
-
|
|
7
|
+
private orgId;
|
|
8
|
+
constructor(apiKey: string, baseUrl?: string, orgId?: 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,6 +31,8 @@ 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>;
|
|
34
|
+
private resolveOrgId;
|
|
35
|
+
private messagesRequest;
|
|
32
36
|
sendMessage(organizationAddress: string, contactAddress: string, text: string, service?: string): Promise<any>;
|
|
33
37
|
sendTemplate(organizationAddress: string, to: string, metaTemplateId: string, components?: any[]): Promise<any>;
|
|
34
38
|
listConversations(filters?: Record<string, string>): Promise<any>;
|
package/dist/client.js
CHANGED
|
@@ -4,12 +4,20 @@
|
|
|
4
4
|
export class MirloClient {
|
|
5
5
|
baseUrl;
|
|
6
6
|
apiKey;
|
|
7
|
-
|
|
7
|
+
orgId = null;
|
|
8
|
+
constructor(apiKey, baseUrl = 'https://api.mirlo.com/v1', orgId) {
|
|
8
9
|
this.apiKey = apiKey;
|
|
9
10
|
this.baseUrl = baseUrl;
|
|
11
|
+
this.orgId = orgId || null;
|
|
12
|
+
}
|
|
13
|
+
getMessagesBaseUrl() {
|
|
14
|
+
// Messages API is v2, not v1
|
|
15
|
+
return this.baseUrl.replace('/v1', '/v2/messages');
|
|
10
16
|
}
|
|
11
17
|
async request(method, path, body) {
|
|
12
|
-
|
|
18
|
+
// CRM API uses org in path: /v1/crm/organizations/{orgId}/...
|
|
19
|
+
const orgId = await this.resolveOrgId();
|
|
20
|
+
const url = `${this.baseUrl}/crm/organizations/${orgId}${path}`;
|
|
13
21
|
const res = await fetch(url, {
|
|
14
22
|
method,
|
|
15
23
|
headers: {
|
|
@@ -27,88 +35,136 @@ export class MirloClient {
|
|
|
27
35
|
// ── CRM: Deals ──
|
|
28
36
|
async listDeals(filters) {
|
|
29
37
|
const params = filters ? '?' + new URLSearchParams(Object.entries(filters).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
30
|
-
return this.request('GET', `/
|
|
38
|
+
return this.request('GET', `/deals${params}`);
|
|
31
39
|
}
|
|
32
40
|
async getDeal(id) {
|
|
33
|
-
return this.request('GET', `/
|
|
41
|
+
return this.request('GET', `/deals/${id}`);
|
|
34
42
|
}
|
|
35
43
|
async createDeal(data) {
|
|
36
|
-
return this.request('POST', '/
|
|
44
|
+
return this.request('POST', '/deals', data);
|
|
37
45
|
}
|
|
38
46
|
async updateDeal(id, data) {
|
|
39
|
-
return this.request('PATCH', `/
|
|
47
|
+
return this.request('PATCH', `/deals/${id}`, data);
|
|
40
48
|
}
|
|
41
49
|
async moveDealStage(id, stageId) {
|
|
42
|
-
return this.request('PATCH', `/
|
|
50
|
+
return this.request('PATCH', `/deals/${id}/stage`, { stage_id: stageId });
|
|
43
51
|
}
|
|
44
52
|
async deleteDeal(id) {
|
|
45
|
-
return this.request('DELETE', `/
|
|
53
|
+
return this.request('DELETE', `/deals/${id}`);
|
|
46
54
|
}
|
|
47
55
|
// ── CRM: Contacts ──
|
|
48
56
|
async listContacts(filters) {
|
|
49
57
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
50
|
-
return this.request('GET', `/
|
|
58
|
+
return this.request('GET', `/contacts${params}`);
|
|
51
59
|
}
|
|
52
60
|
async getContact(id) {
|
|
53
|
-
return this.request('GET', `/
|
|
61
|
+
return this.request('GET', `/contacts/${id}`);
|
|
54
62
|
}
|
|
55
63
|
async createContact(data) {
|
|
56
|
-
return this.request('POST', '/
|
|
64
|
+
return this.request('POST', '/contacts', data);
|
|
57
65
|
}
|
|
58
66
|
async updateContact(id, data) {
|
|
59
|
-
return this.request('PATCH', `/
|
|
67
|
+
return this.request('PATCH', `/contacts/${id}`, data);
|
|
60
68
|
}
|
|
61
69
|
// ── CRM: Accounts ──
|
|
62
70
|
async listAccounts(filters) {
|
|
63
71
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
64
|
-
return this.request('GET', `/
|
|
72
|
+
return this.request('GET', `/accounts${params}`);
|
|
65
73
|
}
|
|
66
74
|
async getAccount(id) {
|
|
67
|
-
return this.request('GET', `/
|
|
75
|
+
return this.request('GET', `/accounts/${id}`);
|
|
68
76
|
}
|
|
69
77
|
async createAccount(data) {
|
|
70
|
-
return this.request('POST', '/
|
|
78
|
+
return this.request('POST', '/accounts', data);
|
|
71
79
|
}
|
|
72
80
|
async updateAccount(id, data) {
|
|
73
|
-
return this.request('PATCH', `/
|
|
81
|
+
return this.request('PATCH', `/accounts/${id}`, data);
|
|
74
82
|
}
|
|
75
83
|
// ── CRM: Tickets ──
|
|
76
84
|
async listTickets(filters) {
|
|
77
85
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
78
|
-
return this.request('GET', `/
|
|
86
|
+
return this.request('GET', `/tickets${params}`);
|
|
79
87
|
}
|
|
80
88
|
async getTicket(id) {
|
|
81
|
-
return this.request('GET', `/
|
|
89
|
+
return this.request('GET', `/tickets/${id}`);
|
|
82
90
|
}
|
|
83
91
|
async createTicket(data) {
|
|
84
|
-
return this.request('POST', '/
|
|
92
|
+
return this.request('POST', '/tickets', data);
|
|
85
93
|
}
|
|
86
94
|
async updateTicket(id, data) {
|
|
87
|
-
return this.request('PATCH', `/
|
|
95
|
+
return this.request('PATCH', `/tickets/${id}`, data);
|
|
88
96
|
}
|
|
89
97
|
async addTicketNote(ticketId, data) {
|
|
90
|
-
return this.request('POST', `/
|
|
98
|
+
return this.request('POST', `/tickets/${ticketId}/notes`, data);
|
|
91
99
|
}
|
|
92
100
|
// ── CRM: Tasks ──
|
|
93
101
|
async listTasks(filters) {
|
|
94
102
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
95
|
-
return this.request('GET', `/
|
|
103
|
+
return this.request('GET', `/tasks${params}`);
|
|
96
104
|
}
|
|
97
105
|
async createTask(data) {
|
|
98
|
-
return this.request('POST', '/
|
|
106
|
+
return this.request('POST', '/tasks', data);
|
|
99
107
|
}
|
|
100
108
|
async completeTask(id) {
|
|
101
|
-
return this.request('PATCH', `/
|
|
109
|
+
return this.request('PATCH', `/tasks/${id}/complete`);
|
|
102
110
|
}
|
|
103
111
|
// ── CRM: Pipelines ──
|
|
104
112
|
async listPipelines() {
|
|
105
|
-
return this.request('GET', '/
|
|
113
|
+
return this.request('GET', '/pipelines');
|
|
106
114
|
}
|
|
107
115
|
// ── Messaging ──
|
|
108
|
-
// Messages API is v2
|
|
116
|
+
// Messages API is v2 at /v2/messages/organizations/{orgId}/...
|
|
117
|
+
async resolveOrgId() {
|
|
118
|
+
if (this.orgId)
|
|
119
|
+
return this.orgId;
|
|
120
|
+
// Resolve org from API key by calling a lightweight endpoint
|
|
121
|
+
// Try the old-style path first (without org in path) as bootstrap
|
|
122
|
+
try {
|
|
123
|
+
const res = await fetch(`${this.baseUrl}/crm/accounts?limit=1`, {
|
|
124
|
+
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
|
125
|
+
});
|
|
126
|
+
if (res.ok) {
|
|
127
|
+
const data = await res.json();
|
|
128
|
+
if (data?.data?.[0]?.organization_id) {
|
|
129
|
+
this.orgId = data.data[0].organization_id;
|
|
130
|
+
return this.orgId;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
// Try contacts
|
|
136
|
+
try {
|
|
137
|
+
const res = await fetch(`${this.baseUrl}/crm/contacts?limit=1`, {
|
|
138
|
+
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
|
139
|
+
});
|
|
140
|
+
if (res.ok) {
|
|
141
|
+
const data = await res.json();
|
|
142
|
+
if (data?.data?.[0]?.organization_id) {
|
|
143
|
+
this.orgId = data.data[0].organization_id;
|
|
144
|
+
return this.orgId;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch { }
|
|
149
|
+
throw new Error('Could not resolve organization_id. Provide --org-id=UUID or ensure your API key has CRM data.');
|
|
150
|
+
}
|
|
151
|
+
async messagesRequest(method, path, body) {
|
|
152
|
+
const orgId = await this.resolveOrgId();
|
|
153
|
+
const msgBase = this.getMessagesBaseUrl();
|
|
154
|
+
const url = `${msgBase}/organizations/${orgId}${path}`;
|
|
155
|
+
const res = await fetch(url, {
|
|
156
|
+
method,
|
|
157
|
+
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
|
158
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
const text = await res.text();
|
|
162
|
+
throw new Error(`Mirlo Messages API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
|
|
163
|
+
}
|
|
164
|
+
return res.json();
|
|
165
|
+
}
|
|
109
166
|
async sendMessage(organizationAddress, contactAddress, text, service = 'whatsapp') {
|
|
110
|
-
|
|
111
|
-
return this.request('POST', '/messages', {
|
|
167
|
+
return this.messagesRequest('POST', '/messages', {
|
|
112
168
|
organization_address: organizationAddress,
|
|
113
169
|
service,
|
|
114
170
|
contact_address: contactAddress,
|
|
@@ -117,7 +173,7 @@ export class MirloClient {
|
|
|
117
173
|
});
|
|
118
174
|
}
|
|
119
175
|
async sendTemplate(organizationAddress, to, metaTemplateId, components) {
|
|
120
|
-
return this.
|
|
176
|
+
return this.messagesRequest('POST', '/messages/send-template', {
|
|
121
177
|
organization_address: organizationAddress,
|
|
122
178
|
to,
|
|
123
179
|
meta_template_id: metaTemplateId,
|
|
@@ -126,10 +182,10 @@ export class MirloClient {
|
|
|
126
182
|
}
|
|
127
183
|
async listConversations(filters) {
|
|
128
184
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
129
|
-
return this.
|
|
185
|
+
return this.messagesRequest('GET', `/conversations${params}`);
|
|
130
186
|
}
|
|
131
187
|
async listMessages(conversationId, limit = 20) {
|
|
132
|
-
return this.
|
|
188
|
+
return this.messagesRequest('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
|
|
133
189
|
}
|
|
134
190
|
// ── Voice ──
|
|
135
191
|
async listCalls(filters) {
|
package/dist/index.js
CHANGED
|
@@ -34,10 +34,17 @@ function getBaseUrl() {
|
|
|
34
34
|
return process.env.MIRLO_BASE_URL || 'https://api.mirlo.com/v1';
|
|
35
35
|
}
|
|
36
36
|
// ── Main ──
|
|
37
|
+
function getOrgId() {
|
|
38
|
+
const cliArg = process.argv.find((a) => a.startsWith('--org-id='));
|
|
39
|
+
if (cliArg)
|
|
40
|
+
return cliArg.split('=')[1];
|
|
41
|
+
return process.env.MIRLO_ORG_ID || undefined;
|
|
42
|
+
}
|
|
37
43
|
async function main() {
|
|
38
44
|
const apiKey = getApiKey();
|
|
39
45
|
const baseUrl = getBaseUrl();
|
|
40
|
-
const
|
|
46
|
+
const orgId = getOrgId();
|
|
47
|
+
const client = new MirloClient(apiKey, baseUrl, orgId);
|
|
41
48
|
const server = new McpServer({
|
|
42
49
|
name: 'mirlo',
|
|
43
50
|
version: '1.0.0',
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -5,14 +5,23 @@
|
|
|
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
|
-
constructor(apiKey: string, baseUrl = 'https://api.mirlo.com/v1') {
|
|
10
|
+
constructor(apiKey: string, baseUrl = 'https://api.mirlo.com/v1', orgId?: string) {
|
|
10
11
|
this.apiKey = apiKey;
|
|
11
12
|
this.baseUrl = baseUrl;
|
|
13
|
+
this.orgId = orgId || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private getMessagesBaseUrl(): string {
|
|
17
|
+
// Messages API is v2, not v1
|
|
18
|
+
return this.baseUrl.replace('/v1', '/v2/messages');
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
15
|
-
|
|
22
|
+
// CRM API uses org in path: /v1/crm/organizations/{orgId}/...
|
|
23
|
+
const orgId = await this.resolveOrgId();
|
|
24
|
+
const url = `${this.baseUrl}/crm/organizations/${orgId}${path}`;
|
|
16
25
|
const res = await fetch(url, {
|
|
17
26
|
method,
|
|
18
27
|
headers: {
|
|
@@ -34,117 +43,164 @@ export class MirloClient {
|
|
|
34
43
|
|
|
35
44
|
async listDeals(filters?: Record<string, string | number | boolean>) {
|
|
36
45
|
const params = filters ? '?' + new URLSearchParams(Object.entries(filters).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
37
|
-
return this.request<any>('GET', `/
|
|
46
|
+
return this.request<any>('GET', `/deals${params}`);
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
async getDeal(id: string) {
|
|
41
|
-
return this.request<any>('GET', `/
|
|
50
|
+
return this.request<any>('GET', `/deals/${id}`);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
async createDeal(data: Record<string, unknown>) {
|
|
45
|
-
return this.request<any>('POST', '/
|
|
54
|
+
return this.request<any>('POST', '/deals', data);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
async updateDeal(id: string, data: Record<string, unknown>) {
|
|
49
|
-
return this.request<any>('PATCH', `/
|
|
58
|
+
return this.request<any>('PATCH', `/deals/${id}`, data);
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
async moveDealStage(id: string, stageId: string) {
|
|
53
|
-
return this.request<any>('PATCH', `/
|
|
62
|
+
return this.request<any>('PATCH', `/deals/${id}/stage`, { stage_id: stageId });
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
async deleteDeal(id: string) {
|
|
57
|
-
return this.request<any>('DELETE', `/
|
|
66
|
+
return this.request<any>('DELETE', `/deals/${id}`);
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
// ── CRM: Contacts ──
|
|
61
70
|
|
|
62
71
|
async listContacts(filters?: Record<string, string>) {
|
|
63
72
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
64
|
-
return this.request<any>('GET', `/
|
|
73
|
+
return this.request<any>('GET', `/contacts${params}`);
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
async getContact(id: string) {
|
|
68
|
-
return this.request<any>('GET', `/
|
|
77
|
+
return this.request<any>('GET', `/contacts/${id}`);
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
async createContact(data: Record<string, unknown>) {
|
|
72
|
-
return this.request<any>('POST', '/
|
|
81
|
+
return this.request<any>('POST', '/contacts', data);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
84
|
async updateContact(id: string, data: Record<string, unknown>) {
|
|
76
|
-
return this.request<any>('PATCH', `/
|
|
85
|
+
return this.request<any>('PATCH', `/contacts/${id}`, data);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
// ── CRM: Accounts ──
|
|
80
89
|
|
|
81
90
|
async listAccounts(filters?: Record<string, string>) {
|
|
82
91
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
83
|
-
return this.request<any>('GET', `/
|
|
92
|
+
return this.request<any>('GET', `/accounts${params}`);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
async getAccount(id: string) {
|
|
87
|
-
return this.request<any>('GET', `/
|
|
96
|
+
return this.request<any>('GET', `/accounts/${id}`);
|
|
88
97
|
}
|
|
89
98
|
|
|
90
99
|
async createAccount(data: Record<string, unknown>) {
|
|
91
|
-
return this.request<any>('POST', '/
|
|
100
|
+
return this.request<any>('POST', '/accounts', data);
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
async updateAccount(id: string, data: Record<string, unknown>) {
|
|
95
|
-
return this.request<any>('PATCH', `/
|
|
104
|
+
return this.request<any>('PATCH', `/accounts/${id}`, data);
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
// ── CRM: Tickets ──
|
|
99
108
|
|
|
100
109
|
async listTickets(filters?: Record<string, string>) {
|
|
101
110
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
102
|
-
return this.request<any>('GET', `/
|
|
111
|
+
return this.request<any>('GET', `/tickets${params}`);
|
|
103
112
|
}
|
|
104
113
|
|
|
105
114
|
async getTicket(id: string) {
|
|
106
|
-
return this.request<any>('GET', `/
|
|
115
|
+
return this.request<any>('GET', `/tickets/${id}`);
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
async createTicket(data: Record<string, unknown>) {
|
|
110
|
-
return this.request<any>('POST', '/
|
|
119
|
+
return this.request<any>('POST', '/tickets', data);
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
async updateTicket(id: string, data: Record<string, unknown>) {
|
|
114
|
-
return this.request<any>('PATCH', `/
|
|
123
|
+
return this.request<any>('PATCH', `/tickets/${id}`, data);
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
async addTicketNote(ticketId: string, data: Record<string, unknown>) {
|
|
118
|
-
return this.request<any>('POST', `/
|
|
127
|
+
return this.request<any>('POST', `/tickets/${ticketId}/notes`, data);
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
// ── CRM: Tasks ──
|
|
122
131
|
|
|
123
132
|
async listTasks(filters?: Record<string, string>) {
|
|
124
133
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
125
|
-
return this.request<any>('GET', `/
|
|
134
|
+
return this.request<any>('GET', `/tasks${params}`);
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
async createTask(data: Record<string, unknown>) {
|
|
129
|
-
return this.request<any>('POST', '/
|
|
138
|
+
return this.request<any>('POST', '/tasks', data);
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
async completeTask(id: string) {
|
|
133
|
-
return this.request<any>('PATCH', `/
|
|
142
|
+
return this.request<any>('PATCH', `/tasks/${id}/complete`);
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
// ── CRM: Pipelines ──
|
|
137
146
|
|
|
138
147
|
async listPipelines() {
|
|
139
|
-
return this.request<any>('GET', '/
|
|
148
|
+
return this.request<any>('GET', '/pipelines');
|
|
140
149
|
}
|
|
141
150
|
|
|
142
151
|
// ── Messaging ──
|
|
143
|
-
// Messages API is v2
|
|
152
|
+
// Messages API is v2 at /v2/messages/organizations/{orgId}/...
|
|
153
|
+
|
|
154
|
+
private async resolveOrgId(): Promise<string> {
|
|
155
|
+
if (this.orgId) return this.orgId;
|
|
156
|
+
// Resolve org from API key by calling a lightweight endpoint
|
|
157
|
+
// Try the old-style path first (without org in path) as bootstrap
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetch(`${this.baseUrl}/crm/accounts?limit=1`, {
|
|
160
|
+
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
|
161
|
+
});
|
|
162
|
+
if (res.ok) {
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
if (data?.data?.[0]?.organization_id) {
|
|
165
|
+
this.orgId = data.data[0].organization_id;
|
|
166
|
+
return this.orgId!;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {}
|
|
170
|
+
// Try contacts
|
|
171
|
+
try {
|
|
172
|
+
const res = await fetch(`${this.baseUrl}/crm/contacts?limit=1`, {
|
|
173
|
+
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
|
174
|
+
});
|
|
175
|
+
if (res.ok) {
|
|
176
|
+
const data = await res.json();
|
|
177
|
+
if (data?.data?.[0]?.organization_id) {
|
|
178
|
+
this.orgId = data.data[0].organization_id;
|
|
179
|
+
return this.orgId!;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
183
|
+
throw new Error('Could not resolve organization_id. Provide --org-id=UUID or ensure your API key has CRM data.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async messagesRequest<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
187
|
+
const orgId = await this.resolveOrgId();
|
|
188
|
+
const msgBase = this.getMessagesBaseUrl();
|
|
189
|
+
const url = `${msgBase}/organizations/${orgId}${path}`;
|
|
190
|
+
const res = await fetch(url, {
|
|
191
|
+
method,
|
|
192
|
+
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
|
193
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
194
|
+
});
|
|
195
|
+
if (!res.ok) {
|
|
196
|
+
const text = await res.text();
|
|
197
|
+
throw new Error(`Mirlo Messages API ${method} ${path} → ${res.status}: ${text.slice(0, 200)}`);
|
|
198
|
+
}
|
|
199
|
+
return res.json() as T;
|
|
200
|
+
}
|
|
144
201
|
|
|
145
202
|
async sendMessage(organizationAddress: string, contactAddress: string, text: string, service = 'whatsapp') {
|
|
146
|
-
|
|
147
|
-
return this.request<any>('POST', '/messages', {
|
|
203
|
+
return this.messagesRequest<any>('POST', '/messages', {
|
|
148
204
|
organization_address: organizationAddress,
|
|
149
205
|
service,
|
|
150
206
|
contact_address: contactAddress,
|
|
@@ -154,7 +210,7 @@ export class MirloClient {
|
|
|
154
210
|
}
|
|
155
211
|
|
|
156
212
|
async sendTemplate(organizationAddress: string, to: string, metaTemplateId: string, components?: any[]) {
|
|
157
|
-
return this.
|
|
213
|
+
return this.messagesRequest<any>('POST', '/messages/send-template', {
|
|
158
214
|
organization_address: organizationAddress,
|
|
159
215
|
to,
|
|
160
216
|
meta_template_id: metaTemplateId,
|
|
@@ -164,11 +220,11 @@ export class MirloClient {
|
|
|
164
220
|
|
|
165
221
|
async listConversations(filters?: Record<string, string>) {
|
|
166
222
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
167
|
-
return this.
|
|
223
|
+
return this.messagesRequest<any>('GET', `/conversations${params}`);
|
|
168
224
|
}
|
|
169
225
|
|
|
170
226
|
async listMessages(conversationId: string, limit = 20) {
|
|
171
|
-
return this.
|
|
227
|
+
return this.messagesRequest<any>('GET', `/conversations/${conversationId}/messages?limit=${limit}`);
|
|
172
228
|
}
|
|
173
229
|
|
|
174
230
|
// ── Voice ──
|
package/src/index.ts
CHANGED
|
@@ -40,10 +40,17 @@ function getBaseUrl(): string {
|
|
|
40
40
|
|
|
41
41
|
// ── Main ──
|
|
42
42
|
|
|
43
|
+
function getOrgId(): string | undefined {
|
|
44
|
+
const cliArg = process.argv.find((a: string) => a.startsWith('--org-id='));
|
|
45
|
+
if (cliArg) return cliArg.split('=')[1];
|
|
46
|
+
return process.env.MIRLO_ORG_ID || undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
async function main() {
|
|
44
50
|
const apiKey = getApiKey();
|
|
45
51
|
const baseUrl = getBaseUrl();
|
|
46
|
-
const
|
|
52
|
+
const orgId = getOrgId();
|
|
53
|
+
const client = new MirloClient(apiKey, baseUrl, orgId);
|
|
47
54
|
|
|
48
55
|
const server = new McpServer({
|
|
49
56
|
name: 'mirlo',
|