@charisol/plexo-mcp 1.0.9 → 1.0.10

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.
@@ -0,0 +1,516 @@
1
+ import { PlexoClient } from "./client/plexoClient.js";
2
+
3
+ const DIGITAL_PRODUCT_NOTE =
4
+ "For a DIGITAL product, digitalDeliveryMethod is required (FILE_DOWNLOAD, EXTERNAL_LINK, or ACCESS_LIST), plus the matching field: digitalFileUrl (FILE_DOWNLOAD — a URL already uploaded via the dashboard's file upload; this tool cannot itself accept raw file bytes), digitalExternalUrl (EXTERNAL_LINK), or digitalAccessInstructions (ACCESS_LIST, optionally with digitalAccessPassword).";
5
+
6
+ export const COMMERCE_TOOL_DEFS = [
7
+ {
8
+ name: "list_commerce_products",
9
+ description: "Lists a site's Commerce products (physical, service, or digital), most recently created first.",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ templateId: { type: "string", description: "The site's home page template id." },
14
+ kind: { type: "string", enum: ["PHYSICAL", "SERVICE", "DIGITAL"], description: "Optional kind filter." },
15
+ activeOnly: { type: "boolean", description: "If true, only returns products that aren't soft-deleted/deactivated." },
16
+ },
17
+ required: ["templateId"],
18
+ },
19
+ },
20
+ {
21
+ name: "get_commerce_product",
22
+ description: "Fetches one Commerce product's full details by id.",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ templateId: { type: "string", description: "The site's home page template id." },
27
+ productId: { type: "string" },
28
+ },
29
+ required: ["templateId", "productId"],
30
+ },
31
+ },
32
+ {
33
+ name: "create_commerce_product",
34
+ description: `Creates a Commerce product. ${DIGITAL_PRODUCT_NOTE}`,
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ templateId: { type: "string", description: "The site's home page template id." },
39
+ name: { type: "string" },
40
+ description: { type: "string" },
41
+ kind: { type: "string", enum: ["PHYSICAL", "SERVICE", "DIGITAL"] },
42
+ priceMinor: { type: "number", description: "Price in the smallest currency unit (e.g. kobo/cents)." },
43
+ imageUrl: { type: "string" },
44
+ category: { type: "string", description: "Category name — created if it doesn't already exist." },
45
+ stockQuantity: { type: "number", description: "PHYSICAL only." },
46
+ durationMinutes: { type: "number", description: "SERVICE only." },
47
+ digitalDeliveryMethod: { type: "string", enum: ["FILE_DOWNLOAD", "EXTERNAL_LINK", "ACCESS_LIST"] },
48
+ digitalFileUrl: { type: "string" },
49
+ digitalFileName: { type: "string" },
50
+ digitalExternalUrl: { type: "string" },
51
+ digitalAccessInstructions: { type: "string" },
52
+ digitalAccessPassword: { type: "string" },
53
+ digitalMaxDownloads: { type: "number" },
54
+ digitalLinkExpiryDays: { type: "number" },
55
+ },
56
+ required: ["templateId", "name", "kind", "priceMinor"],
57
+ },
58
+ },
59
+ {
60
+ name: "update_commerce_product",
61
+ description: `Updates a Commerce product. Only the fields provided are changed. kind can't be changed after creation. ${DIGITAL_PRODUCT_NOTE}`,
62
+ inputSchema: {
63
+ type: "object",
64
+ properties: {
65
+ templateId: { type: "string", description: "The site's home page template id." },
66
+ productId: { type: "string" },
67
+ name: { type: "string" },
68
+ description: { type: "string" },
69
+ active: { type: "boolean" },
70
+ priceMinor: { type: "number" },
71
+ imageUrl: { type: "string" },
72
+ category: { type: "string" },
73
+ stockQuantity: { type: "number" },
74
+ durationMinutes: { type: "number" },
75
+ digitalDeliveryMethod: { type: "string", enum: ["FILE_DOWNLOAD", "EXTERNAL_LINK", "ACCESS_LIST"] },
76
+ digitalFileUrl: { type: "string" },
77
+ digitalFileName: { type: "string" },
78
+ digitalExternalUrl: { type: "string" },
79
+ digitalAccessInstructions: { type: "string" },
80
+ digitalAccessPassword: { type: "string" },
81
+ digitalMaxDownloads: { type: "number" },
82
+ digitalLinkExpiryDays: { type: "number" },
83
+ },
84
+ required: ["templateId", "productId"],
85
+ },
86
+ },
87
+ {
88
+ name: "delete_commerce_product",
89
+ description: "Soft-deletes (deactivates) a Commerce product.",
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ templateId: { type: "string", description: "The site's home page template id." },
94
+ productId: { type: "string", description: "The product to deactivate." },
95
+ },
96
+ required: ["templateId", "productId"],
97
+ },
98
+ },
99
+ {
100
+ name: "get_commerce_settings",
101
+ description: "Fetches a site's Commerce settings: enabled state, storefront slug/URL, store currency, payment provider, Paystack/Stripe mode/keys (masked), MailDrip config, and notification email.",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: { templateId: { type: "string", description: "The site's home page template id." } },
105
+ required: ["templateId"],
106
+ },
107
+ },
108
+ {
109
+ name: "update_commerce_settings",
110
+ description: `Updates a site's Commerce settings. Only the fields provided are changed. paymentProvider: BYO_PAYSTACK (default — the site's own Paystack keys below), PLATFORM_PAYSTACK (Plexo's own Paystack account, no keys needed, proceeds credit the Commerce wallet), BYO_STRIPE (the site's own Stripe keys below), or PLATFORM_STRIPE (Plexo's own Stripe account — requires prior approval, see request_commerce_stripe_access/get_commerce_stripe_access_status). currency can't be changed once the site has any orders. Setting enabled:true auto-provisions a working storefront (shop grid, checkout, order confirmation, order tracking) at /store if one doesn't exist yet — rename its URL any time with storefrontSlug.`,
111
+ inputSchema: {
112
+ type: "object",
113
+ properties: {
114
+ templateId: { type: "string", description: "The site's home page template id." },
115
+ enabled: { type: "boolean", description: "Master on/off switch for Commerce on this site." },
116
+ storefrontSlug: { type: "string", description: "Renames the auto-provisioned storefront page's URL segment (default \"store\", i.e. /store). Commerce must already be enabled. The order-confirmation and track-order pages are never renameable." },
117
+ currency: { type: "string", description: "Site-wide store currency (ISO 4217, e.g. NGN/USD/GBP/EUR/GHS/KES/ZAR). Set this before taking orders — it can't be changed afterward." },
118
+ paymentProvider: { type: "string", enum: ["BYO_PAYSTACK", "PLATFORM_PAYSTACK", "PLATFORM_STRIPE", "BYO_STRIPE"] },
119
+ paystackMode: { type: "string", enum: ["TEST", "LIVE"], description: "Which BYO Paystack key pair is active." },
120
+ paystackTestPublicKey: { type: "string" },
121
+ paystackTestSecretKey: { type: "string" },
122
+ paystackLivePublicKey: { type: "string" },
123
+ paystackLiveSecretKey: { type: "string" },
124
+ stripeMode: { type: "string", enum: ["TEST", "LIVE"], description: "Which BYO Stripe key pair is active." },
125
+ stripeTestPublishableKey: { type: "string" },
126
+ stripeTestSecretKey: { type: "string" },
127
+ stripeTestWebhookSecret: { type: "string" },
128
+ stripeLivePublishableKey: { type: "string" },
129
+ stripeLiveSecretKey: { type: "string" },
130
+ stripeLiveWebhookSecret: { type: "string" },
131
+ maildripApiKey: { type: "string" },
132
+ maildripPaidGroupId: { type: "string" },
133
+ maildripNewsletterGroupId: { type: "string" },
134
+ notificationEmail: { type: "string" },
135
+ },
136
+ required: ["templateId"],
137
+ },
138
+ },
139
+ {
140
+ name: "list_commerce_orders",
141
+ description: "Lists a site's Commerce orders (25 per page, most recent first), with optional status/search filters.",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ templateId: { type: "string", description: "The site's home page template id." },
146
+ status: { type: "string", enum: ["PENDING", "PAID", "FAILED", "REFUNDED", "CANCELLED"], description: "Optional status filter." },
147
+ q: { type: "string", description: "Optional search across order number, customer email, and customer name." },
148
+ page: { type: "number", description: "1-indexed page number." },
149
+ },
150
+ required: ["templateId"],
151
+ },
152
+ },
153
+ {
154
+ name: "get_commerce_order",
155
+ description: "Fetches one Commerce order's full details, including line items, booking (if a service), and digital deliveries (if any digital items).",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ templateId: { type: "string", description: "The site's home page template id." },
160
+ orderId: { type: "string" },
161
+ },
162
+ required: ["templateId", "orderId"],
163
+ },
164
+ },
165
+ {
166
+ name: "update_commerce_order",
167
+ description: "Updates an order's fulfillment status (UNFULFILLED/PROCESSING/READY_FOR_PICKUP/SHIPPED/COMPLETED). Payment status is never settable here — it only changes via the payment webhook or refund_commerce_order.",
168
+ inputSchema: {
169
+ type: "object",
170
+ properties: {
171
+ templateId: { type: "string", description: "The site's home page template id." },
172
+ orderId: { type: "string" },
173
+ fulfillmentStatus: { type: "string", enum: ["UNFULFILLED", "PROCESSING", "READY_FOR_PICKUP", "SHIPPED", "COMPLETED"] },
174
+ },
175
+ required: ["templateId", "orderId", "fulfillmentStatus"],
176
+ },
177
+ },
178
+ {
179
+ name: "refund_commerce_order",
180
+ description: "Refunds a PAID order through the Paystack account it was actually paid through. Paystack-only today — a PLATFORM_STRIPE-paid order will error, since that refund path doesn't exist yet. Does not restock inventory or free a booking slot; that's a separate deliberate decision.",
181
+ inputSchema: {
182
+ type: "object",
183
+ properties: {
184
+ templateId: { type: "string", description: "The site's home page template id." },
185
+ orderId: { type: "string" },
186
+ },
187
+ required: ["templateId", "orderId"],
188
+ },
189
+ },
190
+ {
191
+ name: "resend_digital_delivery",
192
+ description: "Re-sends a digital product's delivery email for an already-paid order, using its existing access link (never generates a new one). Use get_commerce_order first to find the deliveryId.",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: {
196
+ templateId: { type: "string", description: "The site's home page template id." },
197
+ orderId: { type: "string", description: "The order." },
198
+ deliveryId: { type: "string", description: "The digital delivery id (from get_commerce_order's digitalDeliveries)." },
199
+ },
200
+ required: ["templateId", "orderId", "deliveryId"],
201
+ },
202
+ },
203
+ {
204
+ name: "get_commerce_wallet",
205
+ description: "Fetches a site's (or its org's pooled) Commerce wallet balance and recent ledger — the withdrawable balance from Platform Paystack/Platform Stripe sales.",
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: { templateId: { type: "string", description: "The site's home page template id." } },
209
+ required: ["templateId"],
210
+ },
211
+ },
212
+ {
213
+ name: "list_commerce_withdrawals",
214
+ description: "Lists the Commerce wallet's withdrawal requests and their status (PENDING/PROCESSED/REJECTED — processed manually by the Plexo team).",
215
+ inputSchema: {
216
+ type: "object",
217
+ properties: { templateId: { type: "string", description: "The site's home page template id." } },
218
+ required: ["templateId"],
219
+ },
220
+ },
221
+ {
222
+ name: "request_commerce_withdrawal",
223
+ description: "Requests a manual bank-transfer payout of the Commerce wallet balance. Reserves the amount immediately; processed manually by the Plexo team.",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ templateId: { type: "string", description: "The site's home page template id." },
228
+ amountCents: { type: "number", description: "Amount to withdraw, in the smallest currency unit." },
229
+ accountNumber: { type: "string" },
230
+ accountHolderName: { type: "string" },
231
+ bankName: { type: "string" },
232
+ },
233
+ required: ["templateId", "amountCents", "accountNumber", "accountHolderName", "bankName"],
234
+ },
235
+ },
236
+ {
237
+ name: "get_commerce_stripe_access_status",
238
+ description: "Reports the organization's current standing to use Platform Stripe for Commerce checkout (NONE/PENDING/APPROVED/REJECTED). Org-scoped, not per-site.",
239
+ inputSchema: { type: "object", properties: {} },
240
+ },
241
+ {
242
+ name: "request_commerce_stripe_access",
243
+ description: "Requests staff approval to route Commerce checkout through Plexo's own Stripe account for international payments. Org-scoped — approval covers every site in the organization once granted.",
244
+ inputSchema: {
245
+ type: "object",
246
+ properties: {
247
+ reason: { type: "string", description: "Optional — why Stripe is needed." },
248
+ expectedVolume: { type: "string", description: "Optional — expected monthly volume." },
249
+ },
250
+ },
251
+ },
252
+ {
253
+ name: "list_commerce_delivery_methods",
254
+ description: "Lists a site's configured delivery/fulfillment methods (pickup and/or courier options with fees and country/state restrictions), sorted by their display order.",
255
+ inputSchema: {
256
+ type: "object",
257
+ properties: { templateId: { type: "string", description: "The site's home page template id." } },
258
+ required: ["templateId"],
259
+ },
260
+ },
261
+ {
262
+ name: "create_commerce_delivery_method",
263
+ description: "Creates a delivery/fulfillment method a customer can choose at checkout.",
264
+ inputSchema: {
265
+ type: "object",
266
+ properties: {
267
+ templateId: { type: "string", description: "The site's home page template id." },
268
+ name: { type: "string", description: "Shown to the customer at checkout, e.g. \"Store Pickup\" or \"Lagos Courier\"." },
269
+ type: { type: "string", enum: ["PICKUP", "COURIER"] },
270
+ feeMinor: { type: "number", description: "Fee in the smallest currency unit. Defaults to 0." },
271
+ description: { type: "string" },
272
+ countries: { type: "array", items: { type: "string" }, description: "ISO-3166-1 alpha-2 codes this method is restricted to. Omit/empty for worldwide." },
273
+ states: { type: "array", items: { type: "string" }, description: "State/region names this method is restricted to. Omit/empty for unrestricted." },
274
+ estimatedTimeLabel: { type: "string", description: "e.g. \"2-3 business days\"." },
275
+ sortOrder: { type: "number" },
276
+ },
277
+ required: ["templateId", "name", "type"],
278
+ },
279
+ },
280
+ {
281
+ name: "update_commerce_delivery_method",
282
+ description: "Updates a delivery method. Only the fields provided are changed.",
283
+ inputSchema: {
284
+ type: "object",
285
+ properties: {
286
+ templateId: { type: "string", description: "The site's home page template id." },
287
+ deliveryMethodId: { type: "string" },
288
+ name: { type: "string" },
289
+ type: { type: "string", enum: ["PICKUP", "COURIER"] },
290
+ feeMinor: { type: "number" },
291
+ description: { type: "string", description: "Empty string clears it." },
292
+ countries: { type: "array", items: { type: "string" }, description: "Empty array clears the restriction (worldwide)." },
293
+ states: { type: "array", items: { type: "string" }, description: "Empty array clears the restriction." },
294
+ estimatedTimeLabel: { type: "string" },
295
+ status: { type: "string", enum: ["ACTIVE", "INACTIVE"] },
296
+ sortOrder: { type: "number" },
297
+ },
298
+ required: ["templateId", "deliveryMethodId"],
299
+ },
300
+ },
301
+ {
302
+ name: "delete_commerce_delivery_method",
303
+ description: "Permanently deletes a delivery method.",
304
+ inputSchema: {
305
+ type: "object",
306
+ properties: {
307
+ templateId: { type: "string", description: "The site's home page template id." },
308
+ deliveryMethodId: { type: "string" },
309
+ },
310
+ required: ["templateId", "deliveryMethodId"],
311
+ },
312
+ },
313
+ {
314
+ name: "list_commerce_discounts",
315
+ description: "Lists a site's discount codes, most recently created first.",
316
+ inputSchema: {
317
+ type: "object",
318
+ properties: { templateId: { type: "string", description: "The site's home page template id." } },
319
+ required: ["templateId"],
320
+ },
321
+ },
322
+ {
323
+ name: "create_commerce_discount",
324
+ description: "Creates a discount code customers can redeem at checkout.",
325
+ inputSchema: {
326
+ type: "object",
327
+ properties: {
328
+ templateId: { type: "string", description: "The site's home page template id." },
329
+ code: { type: "string", description: "Case-insensitive; stored uppercased. Must be unique on this site." },
330
+ type: { type: "string", enum: ["PERCENT", "FIXED"] },
331
+ value: { type: "number", description: "A positive integer. For PERCENT, out of 100 (and can't exceed 100). For FIXED, the smallest currency unit." },
332
+ expiresAt: { type: "string", description: "Optional ISO 8601 date/time after which the code stops working." },
333
+ usageLimit: { type: "number", description: "Optional max number of redemptions." },
334
+ },
335
+ required: ["templateId", "code", "type", "value"],
336
+ },
337
+ },
338
+ {
339
+ name: "update_commerce_discount",
340
+ description: "Updates a discount code's active state, expiry, or usage limit. code/type/value can't be changed once created — delete and recreate instead.",
341
+ inputSchema: {
342
+ type: "object",
343
+ properties: {
344
+ templateId: { type: "string", description: "The site's home page template id." },
345
+ discountId: { type: "string" },
346
+ active: { type: "boolean" },
347
+ expiresAt: { type: "string", description: "Empty string clears the expiry." },
348
+ usageLimit: { type: "number", description: "0 or omitted-as-empty clears the limit." },
349
+ },
350
+ required: ["templateId", "discountId"],
351
+ },
352
+ },
353
+ {
354
+ name: "delete_commerce_discount",
355
+ description: "Permanently deletes a discount code.",
356
+ inputSchema: {
357
+ type: "object",
358
+ properties: {
359
+ templateId: { type: "string", description: "The site's home page template id." },
360
+ discountId: { type: "string" },
361
+ },
362
+ required: ["templateId", "discountId"],
363
+ },
364
+ },
365
+ {
366
+ name: "list_commerce_customers",
367
+ description: "Lists this site's customers directly from its Paystack account (the merchant account that actually processed their payments) — requires Paystack to be configured on this site.",
368
+ inputSchema: {
369
+ type: "object",
370
+ properties: {
371
+ templateId: { type: "string", description: "The site's home page template id." },
372
+ page: { type: "number", description: "1-indexed page number." },
373
+ },
374
+ required: ["templateId"],
375
+ },
376
+ },
377
+ ];
378
+
379
+ export const COMMERCE_TOOL_NAMES = new Set(COMMERCE_TOOL_DEFS.map((t) => t.name));
380
+
381
+ export async function handleCommerceToolCall(name: string, args: any, client: PlexoClient): Promise<any> {
382
+ const templateId = typeof args?.templateId === "string" ? args.templateId : "";
383
+
384
+ switch (name) {
385
+ case "list_commerce_products":
386
+ return await client.listCommerceProducts(templateId, { kind: args.kind, activeOnly: args.activeOnly });
387
+
388
+ case "get_commerce_product":
389
+ if (!args.productId) throw new Error("productId is required.");
390
+ return await client.getCommerceProduct(templateId, args.productId);
391
+
392
+ case "create_commerce_product": {
393
+ const { templateId: _t, ...payload } = args;
394
+ const result = await client.createCommerceProduct(templateId, payload);
395
+ return { success: true, product: result.product };
396
+ }
397
+
398
+ case "update_commerce_product": {
399
+ if (!args.productId) throw new Error("productId is required.");
400
+ const { templateId: _t, productId, ...payload } = args;
401
+ const result = await client.updateCommerceProduct(templateId, productId, payload);
402
+ return { success: true, product: result.product };
403
+ }
404
+
405
+ case "delete_commerce_product":
406
+ if (!args.productId) throw new Error("productId is required.");
407
+ await client.deleteCommerceProduct(templateId, args.productId);
408
+ return { success: true, deletedProductId: args.productId };
409
+
410
+ case "get_commerce_settings":
411
+ return await client.getCommerceSettings(templateId);
412
+
413
+ case "update_commerce_settings": {
414
+ const { templateId: _t, ...payload } = args;
415
+ return await client.updateCommerceSettings(templateId, payload);
416
+ }
417
+
418
+ case "list_commerce_orders":
419
+ return await client.listCommerceOrders(templateId, { status: args.status, q: args.q, page: args.page });
420
+
421
+ case "get_commerce_order":
422
+ if (!args.orderId) throw new Error("orderId is required.");
423
+ return await client.getCommerceOrder(templateId, args.orderId);
424
+
425
+ case "update_commerce_order": {
426
+ if (!args.orderId || !args.fulfillmentStatus) throw new Error("orderId and fulfillmentStatus are required.");
427
+ const result = await client.updateCommerceOrder(templateId, args.orderId, args.fulfillmentStatus);
428
+ return { success: true, order: result.order };
429
+ }
430
+
431
+ case "refund_commerce_order": {
432
+ if (!args.orderId) throw new Error("orderId is required.");
433
+ const result = await client.refundCommerceOrder(templateId, args.orderId);
434
+ return { success: true, order: result.order };
435
+ }
436
+
437
+ case "resend_digital_delivery":
438
+ if (!args.orderId || !args.deliveryId) throw new Error("orderId and deliveryId are required.");
439
+ return await client.resendDigitalDelivery(templateId, args.orderId, args.deliveryId);
440
+
441
+ case "get_commerce_wallet":
442
+ return await client.getCommerceWallet(templateId);
443
+
444
+ case "list_commerce_withdrawals":
445
+ return await client.listCommerceWithdrawals(templateId);
446
+
447
+ case "request_commerce_withdrawal": {
448
+ if (!args.amountCents || !args.accountNumber || !args.accountHolderName || !args.bankName) {
449
+ throw new Error("amountCents, accountNumber, accountHolderName, and bankName are required.");
450
+ }
451
+ const result = await client.requestCommerceWithdrawal(templateId, {
452
+ amountCents: args.amountCents,
453
+ accountNumber: args.accountNumber,
454
+ accountHolderName: args.accountHolderName,
455
+ bankName: args.bankName,
456
+ });
457
+ return { success: true, request: result.request };
458
+ }
459
+
460
+ case "get_commerce_stripe_access_status":
461
+ return await client.getCommerceStripeAccessStatus();
462
+
463
+ case "request_commerce_stripe_access":
464
+ return await client.requestCommerceStripeAccess({ reason: args.reason, expectedVolume: args.expectedVolume });
465
+
466
+ case "list_commerce_delivery_methods":
467
+ return await client.listCommerceDeliveryMethods(templateId);
468
+
469
+ case "create_commerce_delivery_method": {
470
+ if (!args.name || !args.type) throw new Error("name and type are required.");
471
+ const { templateId: _t, ...payload } = args;
472
+ const result = await client.createCommerceDeliveryMethod(templateId, payload);
473
+ return { success: true, method: result.method };
474
+ }
475
+
476
+ case "update_commerce_delivery_method": {
477
+ if (!args.deliveryMethodId) throw new Error("deliveryMethodId is required.");
478
+ const { templateId: _t, deliveryMethodId, ...payload } = args;
479
+ const result = await client.updateCommerceDeliveryMethod(templateId, deliveryMethodId, payload);
480
+ return { success: true, method: result.method };
481
+ }
482
+
483
+ case "delete_commerce_delivery_method":
484
+ if (!args.deliveryMethodId) throw new Error("deliveryMethodId is required.");
485
+ await client.deleteCommerceDeliveryMethod(templateId, args.deliveryMethodId);
486
+ return { success: true, deletedDeliveryMethodId: args.deliveryMethodId };
487
+
488
+ case "list_commerce_discounts":
489
+ return await client.listCommerceDiscounts(templateId);
490
+
491
+ case "create_commerce_discount": {
492
+ if (!args.code || !args.type || !args.value) throw new Error("code, type, and value are required.");
493
+ const { templateId: _t, ...payload } = args;
494
+ const result = await client.createCommerceDiscount(templateId, payload);
495
+ return { success: true, discount: result.discount };
496
+ }
497
+
498
+ case "update_commerce_discount": {
499
+ if (!args.discountId) throw new Error("discountId is required.");
500
+ const { templateId: _t, discountId, ...payload } = args;
501
+ const result = await client.updateCommerceDiscount(templateId, discountId, payload);
502
+ return { success: true, discount: result.discount };
503
+ }
504
+
505
+ case "delete_commerce_discount":
506
+ if (!args.discountId) throw new Error("discountId is required.");
507
+ await client.deleteCommerceDiscount(templateId, args.discountId);
508
+ return { success: true, deletedDiscountId: args.discountId };
509
+
510
+ case "list_commerce_customers":
511
+ return await client.listCommerceCustomers(templateId, args.page);
512
+
513
+ default:
514
+ throw new Error(`Unknown tool: ${name}`);
515
+ }
516
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { PlexoClient } from "./client/plexoClient.js";
10
10
  import { BLOG_TOOL_DEFS, BLOG_TOOL_NAMES, handleBlogToolCall } from "./blogTools.js";
11
+ import { COMMERCE_TOOL_DEFS, COMMERCE_TOOL_NAMES, handleCommerceToolCall } from "./commerceTools.js";
12
+ import { LMS_TOOL_DEFS, LMS_TOOL_NAMES, handleLmsToolCall } from "./lmsTools.js";
11
13
 
12
14
  const server = new Server(
13
15
  {
@@ -513,6 +515,23 @@ To edit a page's CONTENT (designJson), use update_template with the same templat
513
515
  required: ["templateId"],
514
516
  },
515
517
  },
518
+ {
519
+ name: "delete_template",
520
+ description: "Permanently deletes any landing page or email template by templateId or template name (e.g. 'buzm'). Same underlying operation as delete_landing_page_page. Confirm with user before calling as this action cannot be undone.",
521
+ inputSchema: {
522
+ type: "object",
523
+ properties: {
524
+ templateId: {
525
+ type: "string",
526
+ description: "ID of the template or page to delete (UUID, or exact/partial template name like 'buzm').",
527
+ },
528
+ name: {
529
+ type: "string",
530
+ description: "Optional template name or search phrase if templateId is unknown (e.g. 'buzm').",
531
+ },
532
+ },
533
+ },
534
+ },
516
535
  {
517
536
  name: "delete_landing_page_page",
518
537
  description: "Deletes a page. If it has sub-pages nested under it, they're deleted too (cascade) — the response reports how many. Deleting a site's home page deletes the whole site, including every page nested under it and its published domain link — this can't be undone, confirm with the user first.",
@@ -596,6 +615,8 @@ To edit a page's CONTENT (designJson), use update_template with the same templat
596
615
  },
597
616
  },
598
617
  ...BLOG_TOOL_DEFS,
618
+ ...COMMERCE_TOOL_DEFS,
619
+ ...LMS_TOOL_DEFS,
599
620
  ],
600
621
  };
601
622
  });
@@ -751,8 +772,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
751
772
  };
752
773
  }
753
774
 
775
+ case "delete_template":
754
776
  case "delete_landing_page_page": {
755
- const result = await client.deleteLandingPagePage((args as any)?.templateId);
777
+ const rawInput = ((args as any)?.templateId || (args as any)?.name || "").trim();
778
+ if (!rawInput) throw new Error("templateId or template name is required.");
779
+
780
+ // Same UUID-or-name resolution as the server's own delete_template/
781
+ // delete_landing_page_page handler — try an exact id match first, then fall back to
782
+ // a case-insensitive name search across this account's templates.
783
+ let resolvedId = rawInput;
784
+ const { templates } = await client.listTemplates();
785
+ const byId = (templates || []).find((t: any) => t.id === rawInput);
786
+ if (!byId) {
787
+ const needle = rawInput.toLowerCase();
788
+ const matches = (templates || []).filter((t: any) => t.name?.toLowerCase().includes(needle));
789
+ matches.sort((a: any, b: any) => (b.updatedAt ?? "").localeCompare(a.updatedAt ?? ""));
790
+ if (!matches[0]) throw new Error(`No template found matching "${rawInput}".`);
791
+ resolvedId = matches[0].id;
792
+ }
793
+
794
+ const result = await client.deleteLandingPagePage(resolvedId);
756
795
  return {
757
796
  content: [
758
797
  {
@@ -861,6 +900,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
861
900
  ],
862
901
  };
863
902
  }
903
+ if (COMMERCE_TOOL_NAMES.has(name)) {
904
+ const result = await handleCommerceToolCall(name, (args as any) || {}, client);
905
+ return {
906
+ content: [
907
+ {
908
+ type: "text",
909
+ text: JSON.stringify(result, null, 2),
910
+ },
911
+ ],
912
+ };
913
+ }
914
+ if (LMS_TOOL_NAMES.has(name)) {
915
+ const result = await handleLmsToolCall(name, (args as any) || {}, client);
916
+ return {
917
+ content: [
918
+ {
919
+ type: "text",
920
+ text: JSON.stringify(result, null, 2),
921
+ },
922
+ ],
923
+ };
924
+ }
864
925
  throw new Error(`Unknown tool: ${name}`);
865
926
  }
866
927
  }