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 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
- const url = `${this.baseUrl}${path}`;
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', `/crm/deals${params}`);
38
+ return this.request('GET', `/deals${params}`);
36
39
  }
37
40
  async getDeal(id) {
38
- return this.request('GET', `/crm/deals/${id}`);
41
+ return this.request('GET', `/deals/${id}`);
39
42
  }
40
43
  async createDeal(data) {
41
- return this.request('POST', '/crm/deals', data);
44
+ return this.request('POST', '/deals', data);
42
45
  }
43
46
  async updateDeal(id, data) {
44
- return this.request('PATCH', `/crm/deals/${id}`, data);
47
+ return this.request('PATCH', `/deals/${id}`, data);
45
48
  }
46
49
  async moveDealStage(id, stageId) {
47
- return this.request('PATCH', `/crm/deals/${id}/stage`, { stage_id: stageId });
50
+ return this.request('PATCH', `/deals/${id}/stage`, { stage_id: stageId });
48
51
  }
49
52
  async deleteDeal(id) {
50
- return this.request('DELETE', `/crm/deals/${id}`);
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', `/crm/contacts${params}`);
58
+ return this.request('GET', `/contacts${params}`);
56
59
  }
57
60
  async getContact(id) {
58
- return this.request('GET', `/crm/contacts/${id}`);
61
+ return this.request('GET', `/contacts/${id}`);
59
62
  }
60
63
  async createContact(data) {
61
- return this.request('POST', '/crm/contacts', data);
64
+ return this.request('POST', '/contacts', data);
62
65
  }
63
66
  async updateContact(id, data) {
64
- return this.request('PATCH', `/crm/contacts/${id}`, data);
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', `/crm/accounts${params}`);
72
+ return this.request('GET', `/accounts${params}`);
70
73
  }
71
74
  async getAccount(id) {
72
- return this.request('GET', `/crm/accounts/${id}`);
75
+ return this.request('GET', `/accounts/${id}`);
73
76
  }
74
77
  async createAccount(data) {
75
- return this.request('POST', '/crm/accounts', data);
78
+ return this.request('POST', '/accounts', data);
76
79
  }
77
80
  async updateAccount(id, data) {
78
- return this.request('PATCH', `/crm/accounts/${id}`, data);
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', `/crm/tickets${params}`);
86
+ return this.request('GET', `/tickets${params}`);
84
87
  }
85
88
  async getTicket(id) {
86
- return this.request('GET', `/crm/tickets/${id}`);
89
+ return this.request('GET', `/tickets/${id}`);
87
90
  }
88
91
  async createTicket(data) {
89
- return this.request('POST', '/crm/tickets', data);
92
+ return this.request('POST', '/tickets', data);
90
93
  }
91
94
  async updateTicket(id, data) {
92
- return this.request('PATCH', `/crm/tickets/${id}`, data);
95
+ return this.request('PATCH', `/tickets/${id}`, data);
93
96
  }
94
97
  async addTicketNote(ticketId, data) {
95
- return this.request('POST', `/crm/tickets/${ticketId}/notes`, data);
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', `/crm/tasks${params}`);
103
+ return this.request('GET', `/tasks${params}`);
101
104
  }
102
105
  async createTask(data) {
103
- return this.request('POST', '/crm/tasks', data);
106
+ return this.request('POST', '/tasks', data);
104
107
  }
105
108
  async completeTask(id) {
106
- return this.request('PATCH', `/crm/tasks/${id}/complete`);
109
+ return this.request('PATCH', `/tasks/${id}/complete`);
107
110
  }
108
111
  // ── CRM: Pipelines ──
109
112
  async listPipelines() {
110
- return this.request('GET', '/crm/pipelines');
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.listAccounts({ limit: '1' });
119
- if (res?.data?.[0]?.organization_id) {
120
- this.orgId = res.data[0].organization_id;
121
- return this.orgId;
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.listContacts({ limit: '1' });
127
- if (res?.data?.[0]?.organization_id) {
128
- this.orgId = res.data[0].organization_id;
129
- return this.orgId;
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. Make sure your API key has CRM data.');
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
- const orgId = await this.resolveOrgId();
137
- const msgBase = this.getMessagesBaseUrl();
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 client = new MirloClient(apiKey, baseUrl);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirlo-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
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
@@ -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
- const url = `${this.baseUrl}${path}`;
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', `/crm/deals${params}`);
46
+ return this.request<any>('GET', `/deals${params}`);
44
47
  }
45
48
 
46
49
  async getDeal(id: string) {
47
- return this.request<any>('GET', `/crm/deals/${id}`);
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', '/crm/deals', data);
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', `/crm/deals/${id}`, data);
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', `/crm/deals/${id}/stage`, { stage_id: stageId });
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', `/crm/deals/${id}`);
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', `/crm/contacts${params}`);
73
+ return this.request<any>('GET', `/contacts${params}`);
71
74
  }
72
75
 
73
76
  async getContact(id: string) {
74
- return this.request<any>('GET', `/crm/contacts/${id}`);
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', '/crm/contacts', data);
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', `/crm/contacts/${id}`, data);
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', `/crm/accounts${params}`);
92
+ return this.request<any>('GET', `/accounts${params}`);
90
93
  }
91
94
 
92
95
  async getAccount(id: string) {
93
- return this.request<any>('GET', `/crm/accounts/${id}`);
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', '/crm/accounts', data);
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', `/crm/accounts/${id}`, data);
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', `/crm/tickets${params}`);
111
+ return this.request<any>('GET', `/tickets${params}`);
109
112
  }
110
113
 
111
114
  async getTicket(id: string) {
112
- return this.request<any>('GET', `/crm/tickets/${id}`);
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', '/crm/tickets', data);
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', `/crm/tickets/${id}`, data);
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', `/crm/tickets/${ticketId}/notes`, data);
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', `/crm/tasks${params}`);
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', '/crm/tasks', data);
138
+ return this.request<any>('POST', '/tasks', data);
136
139
  }
137
140
 
138
141
  async completeTask(id: string) {
139
- return this.request<any>('PATCH', `/crm/tasks/${id}/complete`);
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', '/crm/pipelines');
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.listAccounts({ limit: '1' });
155
- if (res?.data?.[0]?.organization_id) {
156
- this.orgId = res.data[0].organization_id;
157
- return this.orgId!;
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.listContacts({ limit: '1' });
162
- if (res?.data?.[0]?.organization_id) {
163
- this.orgId = res.data[0].organization_id;
164
- return this.orgId!;
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. Make sure your API key has CRM data.');
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
- const orgId = await this.resolveOrgId();
172
- const msgBase = this.getMessagesBaseUrl();
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 client = new MirloClient(apiKey, baseUrl);
52
+ const orgId = getOrgId();
53
+ const client = new MirloClient(apiKey, baseUrl, orgId);
47
54
 
48
55
  const server = new McpServer({
49
56
  name: 'mirlo',