@codegame.dev/careerflow-mcp 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/LICENSE +21 -0
- package/README.md +398 -0
- package/dist/client.js +110 -0
- package/dist/client.js.map +1 -0
- package/dist/config.js +37 -0
- package/dist/config.js.map +1 -0
- package/dist/helpers.js +192 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.js +194 -0
- package/dist/index.js.map +1 -0
- package/dist/setup.js +296 -0
- package/dist/setup.js.map +1 -0
- package/dist/tools/admin.js +232 -0
- package/dist/tools/admin.js.map +1 -0
- package/dist/tools/benefit-funds.js +243 -0
- package/dist/tools/benefit-funds.js.map +1 -0
- package/dist/tools/chat-extra.js +172 -0
- package/dist/tools/chat-extra.js.map +1 -0
- package/dist/tools/chat.js +125 -0
- package/dist/tools/chat.js.map +1 -0
- package/dist/tools/cms.js +145 -0
- package/dist/tools/cms.js.map +1 -0
- package/dist/tools/crm-admin.js +269 -0
- package/dist/tools/crm-admin.js.map +1 -0
- package/dist/tools/crm.js +632 -0
- package/dist/tools/crm.js.map +1 -0
- package/dist/tools/dev-tickets.js +94 -0
- package/dist/tools/dev-tickets.js.map +1 -0
- package/dist/tools/forms.js +105 -0
- package/dist/tools/forms.js.map +1 -0
- package/dist/tools/hr.js +517 -0
- package/dist/tools/hr.js.map +1 -0
- package/dist/tools/kanban-extra.js +403 -0
- package/dist/tools/kanban-extra.js.map +1 -0
- package/dist/tools/misc.js +159 -0
- package/dist/tools/misc.js.map +1 -0
- package/dist/tools/projects.js +138 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/tasks.js +342 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/time.js +129 -0
- package/dist/tools/time.js.map +1 -0
- package/dist/tools/workplace.js +110 -0
- package/dist/tools/workplace.js.map +1 -0
- package/dist/tools/workspace.js +199 -0
- package/dist/tools/workspace.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { boolParam, companyIdSchema, extractList, resolveCompanyId, safeHandler, textResult } from "../helpers.js";
|
|
3
|
+
/**
|
|
4
|
+
* CRM tools: contacts (leads and customers), deals, and activities.
|
|
5
|
+
*
|
|
6
|
+
* Contacts carry a dual identity: a `contact_status` of `lead` moves through a
|
|
7
|
+
* pipeline/stage, while `customer` is a settled state. The API derives status
|
|
8
|
+
* from the pipeline/stage rather than accepting it directly on edit, so these
|
|
9
|
+
* tools never expose `contact_status` as a writable field.
|
|
10
|
+
*/
|
|
11
|
+
const contactTypeSchema = z.enum(["individual", "corporation"]);
|
|
12
|
+
const relatedToTypeSchema = z.enum(["Contact", "Deal"]);
|
|
13
|
+
function summariseContact(c) {
|
|
14
|
+
return {
|
|
15
|
+
id: c.id,
|
|
16
|
+
contact_type: c.contact_type,
|
|
17
|
+
contact_status: c.contact_status,
|
|
18
|
+
name: c.contact_type === "corporation"
|
|
19
|
+
? c.corporation_name
|
|
20
|
+
: [c.firstname, c.lastname].filter(Boolean).join(" ") || undefined,
|
|
21
|
+
email: c.email || undefined,
|
|
22
|
+
phone: c.phone || undefined,
|
|
23
|
+
mobile: c.mobile || undefined,
|
|
24
|
+
parent_id: c.parent_id ?? undefined,
|
|
25
|
+
child_count: c.child_count ?? undefined,
|
|
26
|
+
customer_code: c.customer_code || undefined,
|
|
27
|
+
lead_source: c.lead_source_name?.en ?? undefined,
|
|
28
|
+
lead_pipeline: c.lead_pipeline_name?.en ?? undefined,
|
|
29
|
+
lead_stage: c.lead_stage || undefined,
|
|
30
|
+
lead_score: c.lead_score ?? undefined,
|
|
31
|
+
rating: c.rating ?? undefined,
|
|
32
|
+
account_owner: [c.account_owner_firstname, c.account_owner_lastname].filter(Boolean).join(" ") || undefined,
|
|
33
|
+
is_active: c.is_active === undefined ? undefined : Boolean(c.is_active),
|
|
34
|
+
tags: Array.isArray(c.tags) ? c.tags.map((t) => t.name?.en ?? t.name_fa) : undefined,
|
|
35
|
+
created: c.created,
|
|
36
|
+
updated: c.updated
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function summariseDeal(d) {
|
|
40
|
+
return {
|
|
41
|
+
id: d.id,
|
|
42
|
+
deal_name: d.deal_name,
|
|
43
|
+
contact_id: d.contact_id,
|
|
44
|
+
contact_name: d.contact_corporation_name ||
|
|
45
|
+
[d.contact_firstname, d.contact_lastname].filter(Boolean).join(" ") ||
|
|
46
|
+
undefined,
|
|
47
|
+
pipeline_id: d.pipeline_id,
|
|
48
|
+
pipeline: d.pipeline_name?.en ?? undefined,
|
|
49
|
+
stage_id: d.stage_id,
|
|
50
|
+
stage: d.stage_name?.en ?? undefined,
|
|
51
|
+
stage_is_won: d.stage_is_won === undefined ? undefined : Boolean(d.stage_is_won),
|
|
52
|
+
stage_is_lost: d.stage_is_lost === undefined ? undefined : Boolean(d.stage_is_lost),
|
|
53
|
+
amount: d.amount ?? undefined,
|
|
54
|
+
currency_id: d.currency_id ?? undefined,
|
|
55
|
+
probability: d.probability ?? undefined,
|
|
56
|
+
expected_close_date: d.expected_close_date || undefined,
|
|
57
|
+
actual_close_date: d.actual_close_date || undefined,
|
|
58
|
+
assigned_to: [d.assigned_to_firstname, d.assigned_to_lastname].filter(Boolean).join(" ") || undefined,
|
|
59
|
+
next_step: d.next_step || undefined,
|
|
60
|
+
lost_reason: d.lost_reason || undefined,
|
|
61
|
+
created: d.created,
|
|
62
|
+
updated: d.updated
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function summariseActivity(a) {
|
|
66
|
+
return {
|
|
67
|
+
id: a.id,
|
|
68
|
+
activity_type: a.activity_type,
|
|
69
|
+
subject: a.subject,
|
|
70
|
+
related_to_type: a.related_to_type,
|
|
71
|
+
related_to_id: a.related_to_id,
|
|
72
|
+
contact_id: a.contact_id ?? undefined,
|
|
73
|
+
status: a.status,
|
|
74
|
+
priority: a.priority,
|
|
75
|
+
due_date: a.due_date || undefined,
|
|
76
|
+
completed_at: a.completed_at || undefined,
|
|
77
|
+
assigned_to: [a.assigned_to_firstname, a.assigned_to_lastname].filter(Boolean).join(" ") || undefined,
|
|
78
|
+
outcome: a.outcome || undefined,
|
|
79
|
+
created: a.created
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export function registerCrmTools(server, client) {
|
|
83
|
+
// ---------------------------------------------------------------- contacts
|
|
84
|
+
server.registerTool("crm_search_contacts", {
|
|
85
|
+
title: "Search CRM contacts",
|
|
86
|
+
description: "Search leads and customers. A 'lead' moves through lead_pipeline_id/lead_stage_id; a " +
|
|
87
|
+
"'customer' is settled. Use crm_list_pipelines to resolve pipeline/stage ids.",
|
|
88
|
+
inputSchema: {
|
|
89
|
+
company_id: companyIdSchema,
|
|
90
|
+
search: z.string().optional(),
|
|
91
|
+
contact_type: contactTypeSchema.optional(),
|
|
92
|
+
contact_status: z.enum(["lead", "customer", "lost"]).optional(),
|
|
93
|
+
lead_pipeline_id: z.number().int().optional(),
|
|
94
|
+
lead_stage_id: z.number().int().optional(),
|
|
95
|
+
tag_id: z.number().int().optional(),
|
|
96
|
+
parent_id: z.number().int().optional().describe("Sub-contacts of one corporation."),
|
|
97
|
+
is_active: z.boolean().optional().default(true),
|
|
98
|
+
limit: z.number().int().min(1).max(50).optional().default(20),
|
|
99
|
+
page: z.number().int().min(1).optional().default(1),
|
|
100
|
+
order_by: z.string().optional().default("created"),
|
|
101
|
+
direction: z.enum(["asc", "desc"]).optional().default("desc")
|
|
102
|
+
}
|
|
103
|
+
}, safeHandler(async (args) => {
|
|
104
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
105
|
+
const response = await client.get("crm/contacts/search", {
|
|
106
|
+
company_id: companyId,
|
|
107
|
+
search: args.search,
|
|
108
|
+
contact_type: args.contact_type,
|
|
109
|
+
contact_status: args.contact_status,
|
|
110
|
+
lead_pipeline_id: args.lead_pipeline_id,
|
|
111
|
+
lead_stage_id: args.lead_stage_id,
|
|
112
|
+
tag_id: args.tag_id,
|
|
113
|
+
parent_id: args.parent_id,
|
|
114
|
+
is_active: boolParam(args.is_active),
|
|
115
|
+
limit: args.limit,
|
|
116
|
+
page: args.page,
|
|
117
|
+
order_by: args.order_by,
|
|
118
|
+
direction: args.direction
|
|
119
|
+
});
|
|
120
|
+
const { items, total } = extractList(response.data, "contacts");
|
|
121
|
+
return textResult({ count: total ?? items.length, contacts: items.map(summariseContact) });
|
|
122
|
+
}));
|
|
123
|
+
server.registerTool("crm_get_contact", {
|
|
124
|
+
title: "Get a CRM contact",
|
|
125
|
+
description: "Fetch one contact (lead or customer) in full, including tags and custom fields.",
|
|
126
|
+
inputSchema: { company_id: companyIdSchema, contact_id: z.number().int() }
|
|
127
|
+
}, safeHandler(async (args) => {
|
|
128
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
129
|
+
const response = await client.get(`crm/contacts/${args.contact_id}`, {
|
|
130
|
+
company_id: companyId
|
|
131
|
+
});
|
|
132
|
+
return textResult(response.data);
|
|
133
|
+
}));
|
|
134
|
+
server.registerTool("crm_create_contact", {
|
|
135
|
+
title: "Create a CRM contact",
|
|
136
|
+
description: "Create a lead or a customer. Individuals use firstname/lastname; corporations use " +
|
|
137
|
+
"corporation_name. Set parent_id to attach as a sub-contact of an existing corporation " +
|
|
138
|
+
"(inherits its status/pipeline/stage). Leads should include lead_pipeline_id and " +
|
|
139
|
+
"lead_stage_id to place them on a board.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
company_id: companyIdSchema,
|
|
142
|
+
contact_type: contactTypeSchema.optional(),
|
|
143
|
+
parent_id: z.number().int().optional().describe("Attaches as a sub-contact; forces individual type."),
|
|
144
|
+
firstname: z.string().optional(),
|
|
145
|
+
lastname: z.string().optional(),
|
|
146
|
+
corporation_name: z.string().optional(),
|
|
147
|
+
email: z.string().optional(),
|
|
148
|
+
phone: z.string().optional(),
|
|
149
|
+
mobile: z.string().optional(),
|
|
150
|
+
position: z.string().optional(),
|
|
151
|
+
department: z.string().optional(),
|
|
152
|
+
website: z.string().optional(),
|
|
153
|
+
industry: z.string().optional(),
|
|
154
|
+
employee_count: z.number().int().optional(),
|
|
155
|
+
lead_source_id: z.number().int().optional(),
|
|
156
|
+
rating: z.number().int().optional(),
|
|
157
|
+
credit_limit: z.number().optional(),
|
|
158
|
+
payment_terms_days: z.number().int().optional(),
|
|
159
|
+
currency: z.string().length(3).optional(),
|
|
160
|
+
account_owner_user_id: z.number().int().optional(),
|
|
161
|
+
lead_pipeline_id: z.number().int().optional().describe("Must be a pipeline of kind 'lead'."),
|
|
162
|
+
lead_stage_id: z.number().int().optional(),
|
|
163
|
+
address: z.string().optional(),
|
|
164
|
+
city: z.string().optional(),
|
|
165
|
+
state: z.string().optional(),
|
|
166
|
+
postal_code: z.string().optional(),
|
|
167
|
+
country: z.string().optional(),
|
|
168
|
+
is_active: z.boolean().optional().default(true),
|
|
169
|
+
notes: z.string().optional()
|
|
170
|
+
}
|
|
171
|
+
}, safeHandler(async (args) => {
|
|
172
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
173
|
+
const response = await client.post("crm/contacts/create", {
|
|
174
|
+
company_id: companyId,
|
|
175
|
+
contact_type: args.contact_type,
|
|
176
|
+
parent_id: args.parent_id,
|
|
177
|
+
firstname: args.firstname,
|
|
178
|
+
lastname: args.lastname,
|
|
179
|
+
corporation_name: args.corporation_name,
|
|
180
|
+
email: args.email,
|
|
181
|
+
phone: args.phone,
|
|
182
|
+
mobile: args.mobile,
|
|
183
|
+
position: args.position,
|
|
184
|
+
department: args.department,
|
|
185
|
+
website: args.website,
|
|
186
|
+
industry: args.industry,
|
|
187
|
+
employee_count: args.employee_count,
|
|
188
|
+
lead_source_id: args.lead_source_id,
|
|
189
|
+
rating: args.rating,
|
|
190
|
+
credit_limit: args.credit_limit,
|
|
191
|
+
payment_terms_days: args.payment_terms_days,
|
|
192
|
+
currency: args.currency,
|
|
193
|
+
account_owner_user_id: args.account_owner_user_id,
|
|
194
|
+
lead_pipeline_id: args.lead_pipeline_id,
|
|
195
|
+
lead_stage_id: args.lead_stage_id,
|
|
196
|
+
address: args.address,
|
|
197
|
+
city: args.city,
|
|
198
|
+
state: args.state,
|
|
199
|
+
postal_code: args.postal_code,
|
|
200
|
+
country: args.country,
|
|
201
|
+
is_active: boolParam(args.is_active),
|
|
202
|
+
notes: args.notes
|
|
203
|
+
});
|
|
204
|
+
return textResult({ created: true, contact: summariseContact(response.data ?? {}) });
|
|
205
|
+
}));
|
|
206
|
+
server.registerTool("crm_update_contact", {
|
|
207
|
+
title: "Update a CRM contact",
|
|
208
|
+
description: "Change fields on a contact. Only the fields passed are altered. To move a lead through " +
|
|
209
|
+
"its pipeline, pass lead_pipeline_id/lead_stage_id — contact_status is derived from the " +
|
|
210
|
+
"stage automatically, not set directly.",
|
|
211
|
+
inputSchema: {
|
|
212
|
+
company_id: companyIdSchema,
|
|
213
|
+
contact_id: z.number().int(),
|
|
214
|
+
firstname: z.string().optional(),
|
|
215
|
+
lastname: z.string().optional(),
|
|
216
|
+
corporation_name: z.string().optional(),
|
|
217
|
+
email: z.string().optional(),
|
|
218
|
+
phone: z.string().optional(),
|
|
219
|
+
mobile: z.string().optional(),
|
|
220
|
+
position: z.string().optional(),
|
|
221
|
+
rating: z.number().int().optional(),
|
|
222
|
+
account_owner_user_id: z.number().int().optional(),
|
|
223
|
+
lead_pipeline_id: z.number().int().optional(),
|
|
224
|
+
lead_stage_id: z.number().int().optional(),
|
|
225
|
+
lead_stage_note: z.string().optional().describe("Note recorded against the stage change."),
|
|
226
|
+
lost_reason: z.string().optional(),
|
|
227
|
+
is_active: z.boolean().optional(),
|
|
228
|
+
notes: z.string().optional()
|
|
229
|
+
}
|
|
230
|
+
}, safeHandler(async (args) => {
|
|
231
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
232
|
+
const response = await client.post("crm/contacts/edit", {
|
|
233
|
+
company_id: companyId,
|
|
234
|
+
id: args.contact_id,
|
|
235
|
+
firstname: args.firstname,
|
|
236
|
+
lastname: args.lastname,
|
|
237
|
+
corporation_name: args.corporation_name,
|
|
238
|
+
email: args.email,
|
|
239
|
+
phone: args.phone,
|
|
240
|
+
mobile: args.mobile,
|
|
241
|
+
position: args.position,
|
|
242
|
+
rating: args.rating,
|
|
243
|
+
account_owner_user_id: args.account_owner_user_id,
|
|
244
|
+
lead_pipeline_id: args.lead_pipeline_id,
|
|
245
|
+
lead_stage_id: args.lead_stage_id,
|
|
246
|
+
lead_stage_note: args.lead_stage_note,
|
|
247
|
+
lost_reason: args.lost_reason,
|
|
248
|
+
is_active: args.is_active === undefined ? undefined : boolParam(args.is_active),
|
|
249
|
+
notes: args.notes
|
|
250
|
+
});
|
|
251
|
+
return textResult({ updated: true, contact: summariseContact(response.data ?? {}) });
|
|
252
|
+
}));
|
|
253
|
+
server.registerTool("crm_delete_contact", {
|
|
254
|
+
title: "Delete a CRM contact",
|
|
255
|
+
description: "Soft-delete a contact. Its sub-contacts are hidden along with it.",
|
|
256
|
+
inputSchema: { company_id: companyIdSchema, contact_id: z.number().int() }
|
|
257
|
+
}, safeHandler(async (args) => {
|
|
258
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
259
|
+
await client.post(`crm/contacts/delete/${args.contact_id}`, { company_id: companyId });
|
|
260
|
+
return textResult({ deleted: true, contact_id: args.contact_id });
|
|
261
|
+
}));
|
|
262
|
+
server.registerTool("crm_contact_timeline", {
|
|
263
|
+
title: "Get a contact's activity timeline",
|
|
264
|
+
description: "Chronological feed of activities on a contact and on its deals.",
|
|
265
|
+
inputSchema: {
|
|
266
|
+
company_id: companyIdSchema,
|
|
267
|
+
contact_id: z.number().int(),
|
|
268
|
+
limit: z.number().int().min(1).max(100).optional().default(20),
|
|
269
|
+
page: z.number().int().min(1).optional().default(1)
|
|
270
|
+
}
|
|
271
|
+
}, safeHandler(async (args) => {
|
|
272
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
273
|
+
const response = await client.get(`crm/contacts/${args.contact_id}/timeline`, { company_id: companyId, limit: args.limit, page: args.page });
|
|
274
|
+
const { items, total } = extractList(response.data, "activities");
|
|
275
|
+
return textResult({ count: total ?? items.length, activities: items.map(summariseActivity) });
|
|
276
|
+
}));
|
|
277
|
+
// ------------------------------------------------------------------ deals
|
|
278
|
+
server.registerTool("crm_search_deals", {
|
|
279
|
+
title: "Search CRM deals",
|
|
280
|
+
description: "Search deals, optionally by pipeline, stage, contact, or lifecycle status.",
|
|
281
|
+
inputSchema: {
|
|
282
|
+
company_id: companyIdSchema,
|
|
283
|
+
search: z.string().optional(),
|
|
284
|
+
contact_id: z.number().int().optional(),
|
|
285
|
+
pipeline_id: z.number().int().optional(),
|
|
286
|
+
stage_id: z.number().int().optional(),
|
|
287
|
+
assigned_to_user_id: z.number().int().optional(),
|
|
288
|
+
status: z.enum(["open", "won", "lost", "closed", "all"]).optional().default("all"),
|
|
289
|
+
limit: z.number().int().min(1).max(50).optional().default(20),
|
|
290
|
+
page: z.number().int().min(1).optional().default(1),
|
|
291
|
+
order_by: z.string().optional().default("created"),
|
|
292
|
+
direction: z.enum(["asc", "desc"]).optional().default("desc")
|
|
293
|
+
}
|
|
294
|
+
}, safeHandler(async (args) => {
|
|
295
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
296
|
+
const response = await client.get("crm/deals/search", {
|
|
297
|
+
company_id: companyId,
|
|
298
|
+
search: args.search,
|
|
299
|
+
contact_id: args.contact_id,
|
|
300
|
+
pipeline_id: args.pipeline_id,
|
|
301
|
+
stage_id: args.stage_id,
|
|
302
|
+
assigned_to_user_id: args.assigned_to_user_id,
|
|
303
|
+
status: args.status,
|
|
304
|
+
limit: args.limit,
|
|
305
|
+
page: args.page,
|
|
306
|
+
order_by: args.order_by,
|
|
307
|
+
direction: args.direction
|
|
308
|
+
});
|
|
309
|
+
const { items, total } = extractList(response.data, "deals");
|
|
310
|
+
return textResult({ count: total ?? items.length, deals: items.map(summariseDeal) });
|
|
311
|
+
}));
|
|
312
|
+
server.registerTool("crm_get_deal", {
|
|
313
|
+
title: "Get a CRM deal",
|
|
314
|
+
description: "Fetch one deal in full.",
|
|
315
|
+
inputSchema: { company_id: companyIdSchema, deal_id: z.number().int() }
|
|
316
|
+
}, safeHandler(async (args) => {
|
|
317
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
318
|
+
const response = await client.get(`crm/deals/${args.deal_id}`, {
|
|
319
|
+
company_id: companyId
|
|
320
|
+
});
|
|
321
|
+
return textResult(response.data);
|
|
322
|
+
}));
|
|
323
|
+
server.registerTool("crm_create_deal", {
|
|
324
|
+
title: "Create a CRM deal",
|
|
325
|
+
description: "Create a deal on a pipeline/stage. Use crm_list_pipelines to resolve pipeline_id and " +
|
|
326
|
+
"stage_id, and crm_search_contacts to resolve contact_id.",
|
|
327
|
+
inputSchema: {
|
|
328
|
+
company_id: companyIdSchema,
|
|
329
|
+
deal_name: z.string().min(1).max(200),
|
|
330
|
+
contact_id: z.number().int(),
|
|
331
|
+
pipeline_id: z.number().int().describe("Must be a pipeline of kind 'deal'."),
|
|
332
|
+
stage_id: z.number().int(),
|
|
333
|
+
assigned_to_user_id: z.number().int(),
|
|
334
|
+
primary_contact_person_id: z.number().int().optional(),
|
|
335
|
+
amount: z.number().optional().default(0),
|
|
336
|
+
currency_id: z.number().int().optional(),
|
|
337
|
+
expected_close_date: z.string().optional(),
|
|
338
|
+
probability: z.number().int().optional(),
|
|
339
|
+
source_id: z.number().int().optional(),
|
|
340
|
+
description: z.string().optional(),
|
|
341
|
+
next_step: z.string().optional(),
|
|
342
|
+
competitors: z.array(z.string()).optional()
|
|
343
|
+
}
|
|
344
|
+
}, safeHandler(async (args) => {
|
|
345
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
346
|
+
const response = await client.post("crm/deals/create", {
|
|
347
|
+
company_id: companyId,
|
|
348
|
+
deal_name: args.deal_name,
|
|
349
|
+
contact_id: args.contact_id,
|
|
350
|
+
pipeline_id: args.pipeline_id,
|
|
351
|
+
stage_id: args.stage_id,
|
|
352
|
+
assigned_to_user_id: args.assigned_to_user_id,
|
|
353
|
+
primary_contact_person_id: args.primary_contact_person_id,
|
|
354
|
+
amount: args.amount,
|
|
355
|
+
currency_id: args.currency_id,
|
|
356
|
+
expected_close_date: args.expected_close_date,
|
|
357
|
+
probability: args.probability,
|
|
358
|
+
source_id: args.source_id,
|
|
359
|
+
description: args.description,
|
|
360
|
+
next_step: args.next_step,
|
|
361
|
+
competitors: args.competitors ? JSON.stringify(args.competitors) : undefined
|
|
362
|
+
});
|
|
363
|
+
return textResult({ created: true, deal: summariseDeal(response.data ?? {}) });
|
|
364
|
+
}));
|
|
365
|
+
server.registerTool("crm_update_deal", {
|
|
366
|
+
title: "Update a CRM deal",
|
|
367
|
+
description: "Change fields on a deal. Only the fields passed are altered — the deal is read first and " +
|
|
368
|
+
"unmentioned fields keep their current values (the underlying API replaces the whole row, " +
|
|
369
|
+
"so this tool merges for you). Moving into a won/lost stage auto-fills actual_close_date " +
|
|
370
|
+
"unless you pass one.",
|
|
371
|
+
inputSchema: {
|
|
372
|
+
company_id: companyIdSchema,
|
|
373
|
+
deal_id: z.number().int(),
|
|
374
|
+
deal_name: z.string().min(1).max(200).optional(),
|
|
375
|
+
contact_id: z.number().int().optional(),
|
|
376
|
+
pipeline_id: z.number().int().optional(),
|
|
377
|
+
stage_id: z.number().int().optional(),
|
|
378
|
+
assigned_to_user_id: z.number().int().optional(),
|
|
379
|
+
amount: z.number().optional(),
|
|
380
|
+
currency_id: z.number().int().optional(),
|
|
381
|
+
expected_close_date: z.string().optional(),
|
|
382
|
+
actual_close_date: z.string().optional(),
|
|
383
|
+
probability: z.number().int().optional(),
|
|
384
|
+
source_id: z.number().int().optional(),
|
|
385
|
+
description: z.string().optional(),
|
|
386
|
+
next_step: z.string().optional(),
|
|
387
|
+
lost_reason: z.string().optional(),
|
|
388
|
+
stage_note: z.string().optional(),
|
|
389
|
+
competitors: z.array(z.string()).optional().describe("Omit to leave existing competitors unchanged.")
|
|
390
|
+
}
|
|
391
|
+
}, safeHandler(async (args) => {
|
|
392
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
393
|
+
const current = await client.get(`crm/deals/${args.deal_id}`, {
|
|
394
|
+
company_id: companyId
|
|
395
|
+
});
|
|
396
|
+
const deal = current.data ?? {};
|
|
397
|
+
const response = await client.post("crm/deals/edit", {
|
|
398
|
+
company_id: companyId,
|
|
399
|
+
id: args.deal_id,
|
|
400
|
+
deal_name: args.deal_name ?? deal.deal_name,
|
|
401
|
+
contact_id: args.contact_id ?? deal.contact_id,
|
|
402
|
+
pipeline_id: args.pipeline_id ?? deal.pipeline_id,
|
|
403
|
+
stage_id: args.stage_id ?? deal.stage_id,
|
|
404
|
+
assigned_to_user_id: args.assigned_to_user_id ?? deal.assigned_to_user_id,
|
|
405
|
+
amount: args.amount ?? deal.amount,
|
|
406
|
+
currency_id: args.currency_id ?? deal.currency_id,
|
|
407
|
+
expected_close_date: args.expected_close_date ?? deal.expected_close_date,
|
|
408
|
+
actual_close_date: args.actual_close_date ?? deal.actual_close_date,
|
|
409
|
+
probability: args.probability ?? deal.probability,
|
|
410
|
+
source_id: args.source_id ?? deal.source_id,
|
|
411
|
+
description: args.description ?? deal.description,
|
|
412
|
+
next_step: args.next_step ?? deal.next_step,
|
|
413
|
+
lost_reason: args.lost_reason ?? deal.lost_reason,
|
|
414
|
+
stage_note: args.stage_note,
|
|
415
|
+
competitors: args.competitors ? JSON.stringify(args.competitors) : undefined
|
|
416
|
+
});
|
|
417
|
+
return textResult({ updated: true, deal: summariseDeal(response.data ?? {}) });
|
|
418
|
+
}));
|
|
419
|
+
server.registerTool("crm_delete_deal", {
|
|
420
|
+
title: "Delete a CRM deal",
|
|
421
|
+
description: "Soft-delete a deal.",
|
|
422
|
+
inputSchema: { company_id: companyIdSchema, deal_id: z.number().int() }
|
|
423
|
+
}, safeHandler(async (args) => {
|
|
424
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
425
|
+
await client.post(`crm/deals/delete/${args.deal_id}`, { company_id: companyId });
|
|
426
|
+
return textResult({ deleted: true, deal_id: args.deal_id });
|
|
427
|
+
}));
|
|
428
|
+
server.registerTool("crm_deal_stage_summary", {
|
|
429
|
+
title: "Deal pipeline stage summary",
|
|
430
|
+
description: "Per-stage deal count and total amount, for a pipeline board's collapsed view.",
|
|
431
|
+
inputSchema: { company_id: companyIdSchema, pipeline_id: z.number().int().optional() }
|
|
432
|
+
}, safeHandler(async (args) => {
|
|
433
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
434
|
+
const response = await client.get("crm/deals/stage-summary", {
|
|
435
|
+
company_id: companyId,
|
|
436
|
+
pipeline_id: args.pipeline_id
|
|
437
|
+
});
|
|
438
|
+
return textResult(response.data);
|
|
439
|
+
}));
|
|
440
|
+
server.registerTool("crm_add_deal_next_step", {
|
|
441
|
+
title: "Add a deal's next step",
|
|
442
|
+
description: "Record what happens next on a deal. Adds a history entry and updates the deal's current next step.",
|
|
443
|
+
inputSchema: { company_id: companyIdSchema, deal_id: z.number().int(), note: z.string().min(1).max(255) }
|
|
444
|
+
}, safeHandler(async (args) => {
|
|
445
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
446
|
+
const response = await client.post("crm/deals/next-step/create", {
|
|
447
|
+
company_id: companyId,
|
|
448
|
+
deal_id: args.deal_id,
|
|
449
|
+
note: args.note
|
|
450
|
+
});
|
|
451
|
+
return textResult({ deal_id: args.deal_id, next_steps: response.data });
|
|
452
|
+
}));
|
|
453
|
+
server.registerTool("crm_list_deal_line_items", {
|
|
454
|
+
title: "List a deal's line items",
|
|
455
|
+
description: "List the priced line items on a deal.",
|
|
456
|
+
inputSchema: { company_id: companyIdSchema, deal_id: z.number().int() }
|
|
457
|
+
}, safeHandler(async (args) => {
|
|
458
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
459
|
+
const response = await client.get(`crm/deals/${args.deal_id}/line-items`, {
|
|
460
|
+
company_id: companyId
|
|
461
|
+
});
|
|
462
|
+
return textResult(response.data);
|
|
463
|
+
}));
|
|
464
|
+
server.registerTool("crm_add_deal_line_item", {
|
|
465
|
+
title: "Add a deal line item",
|
|
466
|
+
description: "Add a priced line item to a deal. The line total and the deal's overall amount are " +
|
|
467
|
+
"recomputed automatically.",
|
|
468
|
+
inputSchema: {
|
|
469
|
+
company_id: companyIdSchema,
|
|
470
|
+
deal_id: z.number().int(),
|
|
471
|
+
name: z.string().min(1).max(200),
|
|
472
|
+
description: z.string().optional(),
|
|
473
|
+
quantity: z.number().optional().default(1),
|
|
474
|
+
unit_price: z.number().optional().default(0),
|
|
475
|
+
discount_percent: z.number().optional().default(0)
|
|
476
|
+
}
|
|
477
|
+
}, safeHandler(async (args) => {
|
|
478
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
479
|
+
const response = await client.post("crm/deals/line-item/create", {
|
|
480
|
+
company_id: companyId,
|
|
481
|
+
deal_id: args.deal_id,
|
|
482
|
+
name: args.name,
|
|
483
|
+
description: args.description,
|
|
484
|
+
quantity: args.quantity,
|
|
485
|
+
unit_price: args.unit_price,
|
|
486
|
+
discount_percent: args.discount_percent
|
|
487
|
+
});
|
|
488
|
+
return textResult({ created: true, line_item: response.data });
|
|
489
|
+
}));
|
|
490
|
+
server.registerTool("crm_delete_deal_line_item", {
|
|
491
|
+
title: "Delete a deal line item",
|
|
492
|
+
description: "Remove a line item. The deal's amount is recomputed afterward.",
|
|
493
|
+
inputSchema: { company_id: companyIdSchema, line_item_id: z.number().int() }
|
|
494
|
+
}, safeHandler(async (args) => {
|
|
495
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
496
|
+
await client.post(`crm/deals/line-item/delete/${args.line_item_id}`, { company_id: companyId });
|
|
497
|
+
return textResult({ deleted: true, line_item_id: args.line_item_id });
|
|
498
|
+
}));
|
|
499
|
+
// ------------------------------------------------------------- activities
|
|
500
|
+
server.registerTool("crm_search_activities", {
|
|
501
|
+
title: "Search CRM activities",
|
|
502
|
+
description: "Search calls, emails, meetings, tasks and notes logged against contacts or deals.",
|
|
503
|
+
inputSchema: {
|
|
504
|
+
company_id: companyIdSchema,
|
|
505
|
+
search: z.string().optional(),
|
|
506
|
+
activity_type: z.enum(["call", "email", "meeting", "task", "note"]).optional(),
|
|
507
|
+
related_to_type: relatedToTypeSchema.optional(),
|
|
508
|
+
related_to_id: z.number().int().optional(),
|
|
509
|
+
contact_id: z.number().int().optional(),
|
|
510
|
+
status: z.enum(["pending", "completed", "cancelled"]).optional(),
|
|
511
|
+
assigned_to_user_id: z.number().int().optional(),
|
|
512
|
+
priority: z.enum(["low", "medium", "high"]).optional(),
|
|
513
|
+
overdue: z.boolean().optional().describe("Only pending activities past their due date."),
|
|
514
|
+
limit: z.number().int().min(1).max(50).optional().default(20),
|
|
515
|
+
page: z.number().int().min(1).optional().default(1)
|
|
516
|
+
}
|
|
517
|
+
}, safeHandler(async (args) => {
|
|
518
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
519
|
+
const response = await client.get("crm/activities/search", {
|
|
520
|
+
company_id: companyId,
|
|
521
|
+
search: args.search,
|
|
522
|
+
activity_type: args.activity_type,
|
|
523
|
+
related_to_type: args.related_to_type,
|
|
524
|
+
related_to_id: args.related_to_id,
|
|
525
|
+
contact_id: args.contact_id,
|
|
526
|
+
status: args.status,
|
|
527
|
+
assigned_to_user_id: args.assigned_to_user_id,
|
|
528
|
+
priority: args.priority,
|
|
529
|
+
overdue: boolParam(args.overdue),
|
|
530
|
+
limit: args.limit,
|
|
531
|
+
page: args.page
|
|
532
|
+
});
|
|
533
|
+
const { items, total } = extractList(response.data, "activities");
|
|
534
|
+
return textResult({ count: total ?? items.length, activities: items.map(summariseActivity) });
|
|
535
|
+
}));
|
|
536
|
+
server.registerTool("crm_create_activity", {
|
|
537
|
+
title: "Create a CRM activity",
|
|
538
|
+
description: "Log a call, email, meeting, task, or note against a contact or a deal.",
|
|
539
|
+
inputSchema: {
|
|
540
|
+
company_id: companyIdSchema,
|
|
541
|
+
activity_type: z.enum(["call", "email", "meeting", "task", "note"]),
|
|
542
|
+
subject: z.string().min(1).max(200),
|
|
543
|
+
related_to_type: relatedToTypeSchema,
|
|
544
|
+
related_to_id: z.number().int(),
|
|
545
|
+
assigned_to_user_id: z.number().int().optional(),
|
|
546
|
+
due_date: z.string().optional(),
|
|
547
|
+
reminder_at: z.string().optional(),
|
|
548
|
+
status: z.enum(["pending", "completed", "cancelled"]).optional().default("pending"),
|
|
549
|
+
priority: z.enum(["low", "medium", "high"]).optional().default("medium"),
|
|
550
|
+
direction: z.enum(["inbound", "outbound"]).optional(),
|
|
551
|
+
duration_minutes: z.number().int().optional(),
|
|
552
|
+
outcome: z.string().optional()
|
|
553
|
+
}
|
|
554
|
+
}, safeHandler(async (args) => {
|
|
555
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
556
|
+
const response = await client.post("crm/activities/create", {
|
|
557
|
+
company_id: companyId,
|
|
558
|
+
activity_type: args.activity_type,
|
|
559
|
+
subject: args.subject,
|
|
560
|
+
related_to_type: args.related_to_type,
|
|
561
|
+
related_to_id: args.related_to_id,
|
|
562
|
+
assigned_to_user_id: args.assigned_to_user_id,
|
|
563
|
+
due_date: args.due_date,
|
|
564
|
+
reminder_at: args.reminder_at,
|
|
565
|
+
status: args.status,
|
|
566
|
+
priority: args.priority,
|
|
567
|
+
direction: args.direction,
|
|
568
|
+
duration_minutes: args.duration_minutes,
|
|
569
|
+
outcome: args.outcome
|
|
570
|
+
});
|
|
571
|
+
return textResult({ created: true, activity: summariseActivity(response.data ?? {}) });
|
|
572
|
+
}));
|
|
573
|
+
server.registerTool("crm_complete_activity", {
|
|
574
|
+
title: "Complete a CRM activity",
|
|
575
|
+
description: "Mark an activity done, optionally recording its outcome and duration.",
|
|
576
|
+
inputSchema: {
|
|
577
|
+
company_id: companyIdSchema,
|
|
578
|
+
activity_id: z.number().int(),
|
|
579
|
+
outcome: z.string().optional(),
|
|
580
|
+
duration_minutes: z.number().int().optional()
|
|
581
|
+
}
|
|
582
|
+
}, safeHandler(async (args) => {
|
|
583
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
584
|
+
const response = await client.post("crm/activities/complete", {
|
|
585
|
+
company_id: companyId,
|
|
586
|
+
id: args.activity_id,
|
|
587
|
+
outcome: args.outcome,
|
|
588
|
+
duration_minutes: args.duration_minutes
|
|
589
|
+
});
|
|
590
|
+
return textResult({ completed: true, activity: summariseActivity(response.data ?? {}) });
|
|
591
|
+
}));
|
|
592
|
+
server.registerTool("crm_delete_activity", {
|
|
593
|
+
title: "Delete a CRM activity",
|
|
594
|
+
description: "Soft-delete an activity.",
|
|
595
|
+
inputSchema: { company_id: companyIdSchema, activity_id: z.number().int() }
|
|
596
|
+
}, safeHandler(async (args) => {
|
|
597
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
598
|
+
await client.post(`crm/activities/delete/${args.activity_id}`, { company_id: companyId });
|
|
599
|
+
return textResult({ deleted: true, activity_id: args.activity_id });
|
|
600
|
+
}));
|
|
601
|
+
// ------------------------------------------------------------- dashboard
|
|
602
|
+
server.registerTool("crm_dashboard_summary", {
|
|
603
|
+
title: "CRM dashboard summary",
|
|
604
|
+
description: "Win/loss rates, forecast, lead sources and activity stats over a date range.",
|
|
605
|
+
inputSchema: {
|
|
606
|
+
company_id: companyIdSchema,
|
|
607
|
+
from: z.string().optional().describe("YYYY-MM-DD, defaults to 30 days ago."),
|
|
608
|
+
to: z.string().optional().describe("YYYY-MM-DD, defaults to today.")
|
|
609
|
+
}
|
|
610
|
+
}, safeHandler(async (args) => {
|
|
611
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
612
|
+
const response = await client.get("crm/dashboard/summary", {
|
|
613
|
+
company_id: companyId,
|
|
614
|
+
from: args.from,
|
|
615
|
+
to: args.to
|
|
616
|
+
});
|
|
617
|
+
return textResult(response.data);
|
|
618
|
+
}));
|
|
619
|
+
server.registerTool("crm_dashboard_pipeline", {
|
|
620
|
+
title: "CRM pipeline dashboard",
|
|
621
|
+
description: "Per-stage deal breakdown and forecast for one pipeline.",
|
|
622
|
+
inputSchema: { company_id: companyIdSchema, pipeline_id: z.number().int() }
|
|
623
|
+
}, safeHandler(async (args) => {
|
|
624
|
+
const companyId = resolveCompanyId(client, args.company_id);
|
|
625
|
+
const response = await client.get("crm/dashboard/pipeline", {
|
|
626
|
+
company_id: companyId,
|
|
627
|
+
pipeline_id: args.pipeline_id
|
|
628
|
+
});
|
|
629
|
+
return textResult(response.data);
|
|
630
|
+
}));
|
|
631
|
+
}
|
|
632
|
+
//# sourceMappingURL=crm.js.map
|