mirlo-mcp 1.0.4 → 1.1.1
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 +1 -1
- package/dist/client.js +52 -37
- package/dist/index.js +8 -1
- package/package.json +1 -1
- package/src/client.ts +52 -37
- package/src/index.ts +8 -1
package/dist/client.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare class MirloClient {
|
|
|
5
5
|
private readonly baseUrl;
|
|
6
6
|
private readonly apiKey;
|
|
7
7
|
private orgId;
|
|
8
|
-
constructor(apiKey: string, baseUrl?: string);
|
|
8
|
+
constructor(apiKey: string, baseUrl?: string, orgId?: string);
|
|
9
9
|
private getMessagesBaseUrl;
|
|
10
10
|
private request;
|
|
11
11
|
listDeals(filters?: Record<string, string | number | boolean>): Promise<any>;
|
package/dist/client.js
CHANGED
|
@@ -5,16 +5,19 @@ export class MirloClient {
|
|
|
5
5
|
baseUrl;
|
|
6
6
|
apiKey;
|
|
7
7
|
orgId = null;
|
|
8
|
-
constructor(apiKey, baseUrl = 'https://api.mirlo.com/v1') {
|
|
8
|
+
constructor(apiKey, baseUrl = 'https://api.mirlo.com/v1', orgId) {
|
|
9
9
|
this.apiKey = apiKey;
|
|
10
10
|
this.baseUrl = baseUrl;
|
|
11
|
+
this.orgId = orgId || null;
|
|
11
12
|
}
|
|
12
13
|
getMessagesBaseUrl() {
|
|
13
14
|
// Messages API is v2, not v1
|
|
14
15
|
return this.baseUrl.replace('/v1', '/v2/messages');
|
|
15
16
|
}
|
|
16
17
|
async request(method, path, body) {
|
|
17
|
-
|
|
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}`;
|
|
18
21
|
const res = await fetch(url, {
|
|
19
22
|
method,
|
|
20
23
|
headers: {
|
|
@@ -32,110 +35,122 @@ export class MirloClient {
|
|
|
32
35
|
// ── CRM: Deals ──
|
|
33
36
|
async listDeals(filters) {
|
|
34
37
|
const params = filters ? '?' + new URLSearchParams(Object.entries(filters).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
35
|
-
return this.request('GET', `/
|
|
38
|
+
return this.request('GET', `/deals${params}`);
|
|
36
39
|
}
|
|
37
40
|
async getDeal(id) {
|
|
38
|
-
return this.request('GET', `/
|
|
41
|
+
return this.request('GET', `/deals/${id}`);
|
|
39
42
|
}
|
|
40
43
|
async createDeal(data) {
|
|
41
|
-
return this.request('POST', '/
|
|
44
|
+
return this.request('POST', '/deals', data);
|
|
42
45
|
}
|
|
43
46
|
async updateDeal(id, data) {
|
|
44
|
-
return this.request('PATCH', `/
|
|
47
|
+
return this.request('PATCH', `/deals/${id}`, data);
|
|
45
48
|
}
|
|
46
49
|
async moveDealStage(id, stageId) {
|
|
47
|
-
return this.request('PATCH', `/
|
|
50
|
+
return this.request('PATCH', `/deals/${id}/stage`, { stage_id: stageId });
|
|
48
51
|
}
|
|
49
52
|
async deleteDeal(id) {
|
|
50
|
-
return this.request('DELETE', `/
|
|
53
|
+
return this.request('DELETE', `/deals/${id}`);
|
|
51
54
|
}
|
|
52
55
|
// ── CRM: Contacts ──
|
|
53
56
|
async listContacts(filters) {
|
|
54
57
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
55
|
-
return this.request('GET', `/
|
|
58
|
+
return this.request('GET', `/contacts${params}`);
|
|
56
59
|
}
|
|
57
60
|
async getContact(id) {
|
|
58
|
-
return this.request('GET', `/
|
|
61
|
+
return this.request('GET', `/contacts/${id}`);
|
|
59
62
|
}
|
|
60
63
|
async createContact(data) {
|
|
61
|
-
return this.request('POST', '/
|
|
64
|
+
return this.request('POST', '/contacts', data);
|
|
62
65
|
}
|
|
63
66
|
async updateContact(id, data) {
|
|
64
|
-
return this.request('PATCH', `/
|
|
67
|
+
return this.request('PATCH', `/contacts/${id}`, data);
|
|
65
68
|
}
|
|
66
69
|
// ── CRM: Accounts ──
|
|
67
70
|
async listAccounts(filters) {
|
|
68
71
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
69
|
-
return this.request('GET', `/
|
|
72
|
+
return this.request('GET', `/accounts${params}`);
|
|
70
73
|
}
|
|
71
74
|
async getAccount(id) {
|
|
72
|
-
return this.request('GET', `/
|
|
75
|
+
return this.request('GET', `/accounts/${id}`);
|
|
73
76
|
}
|
|
74
77
|
async createAccount(data) {
|
|
75
|
-
return this.request('POST', '/
|
|
78
|
+
return this.request('POST', '/accounts', data);
|
|
76
79
|
}
|
|
77
80
|
async updateAccount(id, data) {
|
|
78
|
-
return this.request('PATCH', `/
|
|
81
|
+
return this.request('PATCH', `/accounts/${id}`, data);
|
|
79
82
|
}
|
|
80
83
|
// ── CRM: Tickets ──
|
|
81
84
|
async listTickets(filters) {
|
|
82
85
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
83
|
-
return this.request('GET', `/
|
|
86
|
+
return this.request('GET', `/tickets${params}`);
|
|
84
87
|
}
|
|
85
88
|
async getTicket(id) {
|
|
86
|
-
return this.request('GET', `/
|
|
89
|
+
return this.request('GET', `/tickets/${id}`);
|
|
87
90
|
}
|
|
88
91
|
async createTicket(data) {
|
|
89
|
-
return this.request('POST', '/
|
|
92
|
+
return this.request('POST', '/tickets', data);
|
|
90
93
|
}
|
|
91
94
|
async updateTicket(id, data) {
|
|
92
|
-
return this.request('PATCH', `/
|
|
95
|
+
return this.request('PATCH', `/tickets/${id}`, data);
|
|
93
96
|
}
|
|
94
97
|
async addTicketNote(ticketId, data) {
|
|
95
|
-
return this.request('POST', `/
|
|
98
|
+
return this.request('POST', `/tickets/${ticketId}/notes`, data);
|
|
96
99
|
}
|
|
97
100
|
// ── CRM: Tasks ──
|
|
98
101
|
async listTasks(filters) {
|
|
99
102
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
100
|
-
return this.request('GET', `/
|
|
103
|
+
return this.request('GET', `/tasks${params}`);
|
|
101
104
|
}
|
|
102
105
|
async createTask(data) {
|
|
103
|
-
return this.request('POST', '/
|
|
106
|
+
return this.request('POST', '/tasks', data);
|
|
104
107
|
}
|
|
105
108
|
async completeTask(id) {
|
|
106
|
-
return this.request('PATCH', `/
|
|
109
|
+
return this.request('PATCH', `/tasks/${id}/complete`);
|
|
107
110
|
}
|
|
108
111
|
// ── CRM: Pipelines ──
|
|
109
112
|
async listPipelines() {
|
|
110
|
-
return this.request('GET', '/
|
|
113
|
+
return this.request('GET', '/pipelines');
|
|
111
114
|
}
|
|
112
115
|
// ── Messaging ──
|
|
113
116
|
// Messages API is v2 at /v2/messages/organizations/{orgId}/...
|
|
114
117
|
async resolveOrgId() {
|
|
115
118
|
if (this.orgId)
|
|
116
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
|
|
117
122
|
try {
|
|
118
|
-
const res = await this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
}
|
|
122
132
|
}
|
|
123
133
|
}
|
|
124
134
|
catch { }
|
|
135
|
+
// Try contacts
|
|
125
136
|
try {
|
|
126
|
-
const res = await this.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
}
|
|
130
146
|
}
|
|
131
147
|
}
|
|
132
148
|
catch { }
|
|
133
|
-
throw new Error('Could not resolve organization_id.
|
|
149
|
+
throw new Error('Could not resolve organization_id. Provide --org-id=UUID or ensure your API key has CRM data.');
|
|
134
150
|
}
|
|
135
151
|
async messagesRequest(method, path, body) {
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
const url = `${msgBase}/organizations/${orgId}${path}`;
|
|
152
|
+
// Messages API v1 — org resolved from API key by APISIX (no org in path)
|
|
153
|
+
const url = `${this.baseUrl}${path}`;
|
|
139
154
|
const res = await fetch(url, {
|
|
140
155
|
method,
|
|
141
156
|
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
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
|
@@ -7,9 +7,10 @@ export class MirloClient {
|
|
|
7
7
|
private readonly apiKey: string;
|
|
8
8
|
private orgId: string | null = null;
|
|
9
9
|
|
|
10
|
-
constructor(apiKey: string, baseUrl = 'https://api.mirlo.com/v1') {
|
|
10
|
+
constructor(apiKey: string, baseUrl = 'https://api.mirlo.com/v1', orgId?: string) {
|
|
11
11
|
this.apiKey = apiKey;
|
|
12
12
|
this.baseUrl = baseUrl;
|
|
13
|
+
this.orgId = orgId || null;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
private getMessagesBaseUrl(): string {
|
|
@@ -18,7 +19,9 @@ export class MirloClient {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
private async request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
21
|
-
|
|
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}`;
|
|
22
25
|
const res = await fetch(url, {
|
|
23
26
|
method,
|
|
24
27
|
headers: {
|
|
@@ -40,109 +43,109 @@ export class MirloClient {
|
|
|
40
43
|
|
|
41
44
|
async listDeals(filters?: Record<string, string | number | boolean>) {
|
|
42
45
|
const params = filters ? '?' + new URLSearchParams(Object.entries(filters).map(([k, v]) => [k, String(v)])).toString() : '';
|
|
43
|
-
return this.request<any>('GET', `/
|
|
46
|
+
return this.request<any>('GET', `/deals${params}`);
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
async getDeal(id: string) {
|
|
47
|
-
return this.request<any>('GET', `/
|
|
50
|
+
return this.request<any>('GET', `/deals/${id}`);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
async createDeal(data: Record<string, unknown>) {
|
|
51
|
-
return this.request<any>('POST', '/
|
|
54
|
+
return this.request<any>('POST', '/deals', data);
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
async updateDeal(id: string, data: Record<string, unknown>) {
|
|
55
|
-
return this.request<any>('PATCH', `/
|
|
58
|
+
return this.request<any>('PATCH', `/deals/${id}`, data);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
async moveDealStage(id: string, stageId: string) {
|
|
59
|
-
return this.request<any>('PATCH', `/
|
|
62
|
+
return this.request<any>('PATCH', `/deals/${id}/stage`, { stage_id: stageId });
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
async deleteDeal(id: string) {
|
|
63
|
-
return this.request<any>('DELETE', `/
|
|
66
|
+
return this.request<any>('DELETE', `/deals/${id}`);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
// ── CRM: Contacts ──
|
|
67
70
|
|
|
68
71
|
async listContacts(filters?: Record<string, string>) {
|
|
69
72
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
70
|
-
return this.request<any>('GET', `/
|
|
73
|
+
return this.request<any>('GET', `/contacts${params}`);
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
async getContact(id: string) {
|
|
74
|
-
return this.request<any>('GET', `/
|
|
77
|
+
return this.request<any>('GET', `/contacts/${id}`);
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
async createContact(data: Record<string, unknown>) {
|
|
78
|
-
return this.request<any>('POST', '/
|
|
81
|
+
return this.request<any>('POST', '/contacts', data);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
async updateContact(id: string, data: Record<string, unknown>) {
|
|
82
|
-
return this.request<any>('PATCH', `/
|
|
85
|
+
return this.request<any>('PATCH', `/contacts/${id}`, data);
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
// ── CRM: Accounts ──
|
|
86
89
|
|
|
87
90
|
async listAccounts(filters?: Record<string, string>) {
|
|
88
91
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
89
|
-
return this.request<any>('GET', `/
|
|
92
|
+
return this.request<any>('GET', `/accounts${params}`);
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
async getAccount(id: string) {
|
|
93
|
-
return this.request<any>('GET', `/
|
|
96
|
+
return this.request<any>('GET', `/accounts/${id}`);
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
async createAccount(data: Record<string, unknown>) {
|
|
97
|
-
return this.request<any>('POST', '/
|
|
100
|
+
return this.request<any>('POST', '/accounts', data);
|
|
98
101
|
}
|
|
99
102
|
|
|
100
103
|
async updateAccount(id: string, data: Record<string, unknown>) {
|
|
101
|
-
return this.request<any>('PATCH', `/
|
|
104
|
+
return this.request<any>('PATCH', `/accounts/${id}`, data);
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
// ── CRM: Tickets ──
|
|
105
108
|
|
|
106
109
|
async listTickets(filters?: Record<string, string>) {
|
|
107
110
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
108
|
-
return this.request<any>('GET', `/
|
|
111
|
+
return this.request<any>('GET', `/tickets${params}`);
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
async getTicket(id: string) {
|
|
112
|
-
return this.request<any>('GET', `/
|
|
115
|
+
return this.request<any>('GET', `/tickets/${id}`);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
118
|
async createTicket(data: Record<string, unknown>) {
|
|
116
|
-
return this.request<any>('POST', '/
|
|
119
|
+
return this.request<any>('POST', '/tickets', data);
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
async updateTicket(id: string, data: Record<string, unknown>) {
|
|
120
|
-
return this.request<any>('PATCH', `/
|
|
123
|
+
return this.request<any>('PATCH', `/tickets/${id}`, data);
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
async addTicketNote(ticketId: string, data: Record<string, unknown>) {
|
|
124
|
-
return this.request<any>('POST', `/
|
|
127
|
+
return this.request<any>('POST', `/tickets/${ticketId}/notes`, data);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
// ── CRM: Tasks ──
|
|
128
131
|
|
|
129
132
|
async listTasks(filters?: Record<string, string>) {
|
|
130
133
|
const params = filters ? '?' + new URLSearchParams(filters).toString() : '';
|
|
131
|
-
return this.request<any>('GET', `/
|
|
134
|
+
return this.request<any>('GET', `/tasks${params}`);
|
|
132
135
|
}
|
|
133
136
|
|
|
134
137
|
async createTask(data: Record<string, unknown>) {
|
|
135
|
-
return this.request<any>('POST', '/
|
|
138
|
+
return this.request<any>('POST', '/tasks', data);
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
async completeTask(id: string) {
|
|
139
|
-
return this.request<any>('PATCH', `/
|
|
142
|
+
return this.request<any>('PATCH', `/tasks/${id}/complete`);
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// ── CRM: Pipelines ──
|
|
143
146
|
|
|
144
147
|
async listPipelines() {
|
|
145
|
-
return this.request<any>('GET', '/
|
|
148
|
+
return this.request<any>('GET', '/pipelines');
|
|
146
149
|
}
|
|
147
150
|
|
|
148
151
|
// ── Messaging ──
|
|
@@ -150,27 +153,39 @@ export class MirloClient {
|
|
|
150
153
|
|
|
151
154
|
private async resolveOrgId(): Promise<string> {
|
|
152
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
|
|
153
158
|
try {
|
|
154
|
-
const res = await this.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
}
|
|
158
168
|
}
|
|
159
169
|
} catch {}
|
|
170
|
+
// Try contacts
|
|
160
171
|
try {
|
|
161
|
-
const res = await this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
+
}
|
|
165
181
|
}
|
|
166
182
|
} catch {}
|
|
167
|
-
throw new Error('Could not resolve organization_id.
|
|
183
|
+
throw new Error('Could not resolve organization_id. Provide --org-id=UUID or ensure your API key has CRM data.');
|
|
168
184
|
}
|
|
169
185
|
|
|
170
186
|
private async messagesRequest<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
const url = `${msgBase}/organizations/${orgId}${path}`;
|
|
187
|
+
// Messages API v1 — org resolved from API key by APISIX (no org in path)
|
|
188
|
+
const url = `${this.baseUrl}${path}`;
|
|
174
189
|
const res = await fetch(url, {
|
|
175
190
|
method,
|
|
176
191
|
headers: { 'X-API-Key': this.apiKey, 'Content-Type': 'application/json' },
|
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',
|