@charisol/plexo-mcp 1.0.9 → 1.0.11
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/README.md +5 -0
- package/dist/blogTools.js +14 -11
- package/dist/client/plexoClient.js +269 -0
- package/dist/commerceTools.js +523 -0
- package/dist/donationTools.js +93 -0
- package/dist/index.js +145 -38
- package/dist/lmsTools.js +254 -0
- package/dist/wpMigrationTools.js +194 -0
- package/package.json +1 -1
- package/src/blogTools.ts +14 -11
- package/src/client/plexoClient.ts +321 -0
- package/src/commerceTools.ts +538 -0
- package/src/donationTools.ts +98 -0
- package/src/index.ts +146 -39
- package/src/lmsTools.ts +262 -0
- package/src/wpMigrationTools.ts +198 -0
|
@@ -0,0 +1,538 @@
|
|
|
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: "resolve_bank_transfer_order",
|
|
315
|
+
description: "Confirms or rejects a bank-transfer order that's waiting for payment (status PENDING, paymentMethod BANK_TRANSFER). action \"confirm\" marks it PAID and runs the normal paid flow (customer confirmation email, digital delivery, booking confirmed) — only use it once the site owner says the money has actually arrived in their account. action \"reject\" cancels it, releases its stock/booking slot, and emails the customer (optional reason is included).",
|
|
316
|
+
inputSchema: {
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
templateId: { type: "string", description: "The site's home page template id." },
|
|
320
|
+
orderId: { type: "string" },
|
|
321
|
+
action: { type: "string", enum: ["confirm", "reject"] },
|
|
322
|
+
reason: { type: "string", description: "Optional note to the customer when rejecting." },
|
|
323
|
+
},
|
|
324
|
+
required: ["templateId", "orderId", "action"],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "list_commerce_discounts",
|
|
329
|
+
description: "Lists a site's discount codes, most recently created first.",
|
|
330
|
+
inputSchema: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: { templateId: { type: "string", description: "The site's home page template id." } },
|
|
333
|
+
required: ["templateId"],
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "create_commerce_discount",
|
|
338
|
+
description: "Creates a discount code customers can redeem at checkout.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
templateId: { type: "string", description: "The site's home page template id." },
|
|
343
|
+
code: { type: "string", description: "Case-insensitive; stored uppercased. Must be unique on this site." },
|
|
344
|
+
type: { type: "string", enum: ["PERCENT", "FIXED"] },
|
|
345
|
+
value: { type: "number", description: "A positive integer. For PERCENT, out of 100 (and can't exceed 100). For FIXED, the smallest currency unit." },
|
|
346
|
+
expiresAt: { type: "string", description: "Optional ISO 8601 date/time after which the code stops working." },
|
|
347
|
+
usageLimit: { type: "number", description: "Optional max number of redemptions." },
|
|
348
|
+
},
|
|
349
|
+
required: ["templateId", "code", "type", "value"],
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: "update_commerce_discount",
|
|
354
|
+
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.",
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
templateId: { type: "string", description: "The site's home page template id." },
|
|
359
|
+
discountId: { type: "string" },
|
|
360
|
+
active: { type: "boolean" },
|
|
361
|
+
expiresAt: { type: "string", description: "Empty string clears the expiry." },
|
|
362
|
+
usageLimit: { type: "number", description: "0 or omitted-as-empty clears the limit." },
|
|
363
|
+
},
|
|
364
|
+
required: ["templateId", "discountId"],
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
name: "delete_commerce_discount",
|
|
369
|
+
description: "Permanently deletes a discount code.",
|
|
370
|
+
inputSchema: {
|
|
371
|
+
type: "object",
|
|
372
|
+
properties: {
|
|
373
|
+
templateId: { type: "string", description: "The site's home page template id." },
|
|
374
|
+
discountId: { type: "string" },
|
|
375
|
+
},
|
|
376
|
+
required: ["templateId", "discountId"],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "list_commerce_customers",
|
|
381
|
+
description: "Lists everyone who's completed a paid order with this site, aggregated from its own orders (works for Stripe or Paystack, however the customer paid).",
|
|
382
|
+
inputSchema: {
|
|
383
|
+
type: "object",
|
|
384
|
+
properties: {
|
|
385
|
+
templateId: { type: "string", description: "The site's home page template id." },
|
|
386
|
+
page: { type: "number", description: "1-indexed page number." },
|
|
387
|
+
},
|
|
388
|
+
required: ["templateId"],
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
export const COMMERCE_TOOL_NAMES = new Set(COMMERCE_TOOL_DEFS.map((t) => t.name));
|
|
394
|
+
|
|
395
|
+
export async function handleCommerceToolCall(name: string, args: any, client: PlexoClient): Promise<any> {
|
|
396
|
+
const templateId = typeof args?.templateId === "string" ? args.templateId : "";
|
|
397
|
+
|
|
398
|
+
switch (name) {
|
|
399
|
+
case "list_commerce_products":
|
|
400
|
+
return await client.listCommerceProducts(templateId, { kind: args.kind, activeOnly: args.activeOnly });
|
|
401
|
+
|
|
402
|
+
case "get_commerce_product":
|
|
403
|
+
if (!args.productId) throw new Error("productId is required.");
|
|
404
|
+
return await client.getCommerceProduct(templateId, args.productId);
|
|
405
|
+
|
|
406
|
+
case "create_commerce_product": {
|
|
407
|
+
const { templateId: _t, ...payload } = args;
|
|
408
|
+
const result = await client.createCommerceProduct(templateId, payload);
|
|
409
|
+
return { success: true, product: result.product };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
case "update_commerce_product": {
|
|
413
|
+
if (!args.productId) throw new Error("productId is required.");
|
|
414
|
+
const { templateId: _t, productId, ...payload } = args;
|
|
415
|
+
const result = await client.updateCommerceProduct(templateId, productId, payload);
|
|
416
|
+
return { success: true, product: result.product };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
case "delete_commerce_product":
|
|
420
|
+
if (!args.productId) throw new Error("productId is required.");
|
|
421
|
+
await client.deleteCommerceProduct(templateId, args.productId);
|
|
422
|
+
return { success: true, deletedProductId: args.productId };
|
|
423
|
+
|
|
424
|
+
case "get_commerce_settings":
|
|
425
|
+
return await client.getCommerceSettings(templateId);
|
|
426
|
+
|
|
427
|
+
case "update_commerce_settings": {
|
|
428
|
+
const { templateId: _t, ...payload } = args;
|
|
429
|
+
return await client.updateCommerceSettings(templateId, payload);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
case "list_commerce_orders":
|
|
433
|
+
return await client.listCommerceOrders(templateId, { status: args.status, q: args.q, page: args.page });
|
|
434
|
+
|
|
435
|
+
case "get_commerce_order":
|
|
436
|
+
if (!args.orderId) throw new Error("orderId is required.");
|
|
437
|
+
return await client.getCommerceOrder(templateId, args.orderId);
|
|
438
|
+
|
|
439
|
+
case "update_commerce_order": {
|
|
440
|
+
if (!args.orderId || !args.fulfillmentStatus) throw new Error("orderId and fulfillmentStatus are required.");
|
|
441
|
+
const result = await client.updateCommerceOrder(templateId, args.orderId, args.fulfillmentStatus);
|
|
442
|
+
return { success: true, order: result.order };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
case "refund_commerce_order": {
|
|
446
|
+
if (!args.orderId) throw new Error("orderId is required.");
|
|
447
|
+
const result = await client.refundCommerceOrder(templateId, args.orderId);
|
|
448
|
+
return { success: true, order: result.order };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
case "resend_digital_delivery":
|
|
452
|
+
if (!args.orderId || !args.deliveryId) throw new Error("orderId and deliveryId are required.");
|
|
453
|
+
return await client.resendDigitalDelivery(templateId, args.orderId, args.deliveryId);
|
|
454
|
+
|
|
455
|
+
case "get_commerce_wallet":
|
|
456
|
+
return await client.getCommerceWallet(templateId);
|
|
457
|
+
|
|
458
|
+
case "list_commerce_withdrawals":
|
|
459
|
+
return await client.listCommerceWithdrawals(templateId);
|
|
460
|
+
|
|
461
|
+
case "request_commerce_withdrawal": {
|
|
462
|
+
if (!args.amountCents || !args.accountNumber || !args.accountHolderName || !args.bankName) {
|
|
463
|
+
throw new Error("amountCents, accountNumber, accountHolderName, and bankName are required.");
|
|
464
|
+
}
|
|
465
|
+
const result = await client.requestCommerceWithdrawal(templateId, {
|
|
466
|
+
amountCents: args.amountCents,
|
|
467
|
+
accountNumber: args.accountNumber,
|
|
468
|
+
accountHolderName: args.accountHolderName,
|
|
469
|
+
bankName: args.bankName,
|
|
470
|
+
});
|
|
471
|
+
return { success: true, request: result.request };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
case "get_commerce_stripe_access_status":
|
|
475
|
+
return await client.getCommerceStripeAccessStatus();
|
|
476
|
+
|
|
477
|
+
case "request_commerce_stripe_access":
|
|
478
|
+
return await client.requestCommerceStripeAccess({ reason: args.reason, expectedVolume: args.expectedVolume });
|
|
479
|
+
|
|
480
|
+
case "list_commerce_delivery_methods":
|
|
481
|
+
return await client.listCommerceDeliveryMethods(templateId);
|
|
482
|
+
|
|
483
|
+
case "create_commerce_delivery_method": {
|
|
484
|
+
if (!args.name || !args.type) throw new Error("name and type are required.");
|
|
485
|
+
const { templateId: _t, ...payload } = args;
|
|
486
|
+
const result = await client.createCommerceDeliveryMethod(templateId, payload);
|
|
487
|
+
return { success: true, method: result.method };
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
case "update_commerce_delivery_method": {
|
|
491
|
+
if (!args.deliveryMethodId) throw new Error("deliveryMethodId is required.");
|
|
492
|
+
const { templateId: _t, deliveryMethodId, ...payload } = args;
|
|
493
|
+
const result = await client.updateCommerceDeliveryMethod(templateId, deliveryMethodId, payload);
|
|
494
|
+
return { success: true, method: result.method };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
case "delete_commerce_delivery_method":
|
|
498
|
+
if (!args.deliveryMethodId) throw new Error("deliveryMethodId is required.");
|
|
499
|
+
await client.deleteCommerceDeliveryMethod(templateId, args.deliveryMethodId);
|
|
500
|
+
return { success: true, deletedDeliveryMethodId: args.deliveryMethodId };
|
|
501
|
+
|
|
502
|
+
case "resolve_bank_transfer_order": {
|
|
503
|
+
const orderId = typeof args.orderId === "string" ? args.orderId.trim() : "";
|
|
504
|
+
if (!orderId) throw new Error("orderId is required.");
|
|
505
|
+
if (args.action !== "confirm" && args.action !== "reject") throw new Error('action must be "confirm" or "reject".');
|
|
506
|
+
const result = await client.resolveBankTransferOrder(templateId, orderId, args.action, typeof args.reason === "string" ? args.reason : undefined);
|
|
507
|
+
return { success: true, order: result.order };
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
case "list_commerce_discounts":
|
|
511
|
+
return await client.listCommerceDiscounts(templateId);
|
|
512
|
+
|
|
513
|
+
case "create_commerce_discount": {
|
|
514
|
+
if (!args.code || !args.type || !args.value) throw new Error("code, type, and value are required.");
|
|
515
|
+
const { templateId: _t, ...payload } = args;
|
|
516
|
+
const result = await client.createCommerceDiscount(templateId, payload);
|
|
517
|
+
return { success: true, discount: result.discount };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
case "update_commerce_discount": {
|
|
521
|
+
if (!args.discountId) throw new Error("discountId is required.");
|
|
522
|
+
const { templateId: _t, discountId, ...payload } = args;
|
|
523
|
+
const result = await client.updateCommerceDiscount(templateId, discountId, payload);
|
|
524
|
+
return { success: true, discount: result.discount };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
case "delete_commerce_discount":
|
|
528
|
+
if (!args.discountId) throw new Error("discountId is required.");
|
|
529
|
+
await client.deleteCommerceDiscount(templateId, args.discountId);
|
|
530
|
+
return { success: true, deletedDiscountId: args.discountId };
|
|
531
|
+
|
|
532
|
+
case "list_commerce_customers":
|
|
533
|
+
return await client.listCommerceCustomers(templateId, args.page);
|
|
534
|
+
|
|
535
|
+
default:
|
|
536
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { PlexoClient } from "./client/plexoClient.js";
|
|
2
|
+
|
|
3
|
+
// Plexo Donations — same tools, same descriptions as the live connector in plexo-web
|
|
4
|
+
// (lib/mcp/donationTools.ts), executed through plexo-web's REST API. Amounts are in the
|
|
5
|
+
// smallest currency unit, like every other Commerce tool.
|
|
6
|
+
|
|
7
|
+
const CAMPAIGN_FIELDS = {
|
|
8
|
+
name: { type: "string", description: "What donors are giving to, e.g. 'Support our mission'." },
|
|
9
|
+
description: { type: "string", description: "Optional one-line description shown on the form." },
|
|
10
|
+
presetAmounts: {
|
|
11
|
+
type: "array",
|
|
12
|
+
description: "Suggested amounts in the smallest currency unit, optionally named — e.g. [{\"amountMinor\":2500,\"label\":\"Bronze\"},{\"amountMinor\":5000}].",
|
|
13
|
+
items: { type: "object", properties: { amountMinor: { type: "number" }, label: { type: "string" } }, required: ["amountMinor"] },
|
|
14
|
+
},
|
|
15
|
+
defaultPresetIndex: { type: "number", description: "Which suggested amount is pre-selected (0-based)." },
|
|
16
|
+
allowCustomAmount: { type: "boolean", description: "Let donors type their own amount (default true)." },
|
|
17
|
+
minAmountMinor: { type: "number", description: "Smallest custom amount, smallest currency unit (default 100)." },
|
|
18
|
+
allowMonthly: { type: "boolean", description: "Offer monthly giving (default true; needs Stripe or Paystack connected)." },
|
|
19
|
+
defaultFrequency: { type: "string", enum: ["ONCE", "MONTHLY"], description: "Which frequency is pre-selected." },
|
|
20
|
+
goalAmountMinor: { type: ["number", "null"], description: "Optional fundraising goal — shows a progress bar." },
|
|
21
|
+
thankYouMessage: { type: "string", description: "Shown after donating and in the receipt email." },
|
|
22
|
+
buttonLabel: { type: "string", description: "Donate button text (default 'Donate')." },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const DONATION_TOOL_DEFS = [
|
|
26
|
+
{
|
|
27
|
+
name: "list_donations",
|
|
28
|
+
description:
|
|
29
|
+
"Shows a site's donations: totals (raised, donors, monthly donors), every donation form (campaign) with its amounts and progress, monthly donors, and recent gifts. Also says whether payments are connected yet (paymentsReady) — if not, tell the owner to connect Stripe/Paystack or add bank details in Commerce → Settings → Payments.",
|
|
30
|
+
inputSchema: { type: "object", properties: { templateId: { type: "string", description: "The site's home page id (see list_landing_pages)." } }, required: ["templateId"] },
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "create_donation_campaign",
|
|
34
|
+
description:
|
|
35
|
+
"Creates a donation form (campaign) for a site — one-time and optional monthly giving — and switches on everything donations need (no shop is created). Returns the campaign id and the embed snippet `<div data-plexo-donation=\"<id>\"></div>`: add that HTML to any page (e.g. via update_landing_page_page or a raw HTML page) to show a working donate form styled to the site.",
|
|
36
|
+
inputSchema: { type: "object", properties: { templateId: { type: "string", description: "The site's home page id." }, ...CAMPAIGN_FIELDS }, required: ["templateId", "name", "presetAmounts"] },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "update_donation_campaign",
|
|
40
|
+
description: "Edits a donation campaign. Only the fields you pass change. Pass active:false to turn a form off (gifts history is kept).",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: { templateId: { type: "string" }, campaignId: { type: "string" }, active: { type: "boolean" }, ...CAMPAIGN_FIELDS },
|
|
44
|
+
required: ["templateId", "campaignId"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "cancel_monthly_donation",
|
|
49
|
+
description: "Stops one donor's monthly gift (at Stripe/Paystack too) — they won't be charged again. Only do this when the owner asks; confirm the donor first using list_donations.",
|
|
50
|
+
inputSchema: { type: "object", properties: { templateId: { type: "string" }, subscriptionId: { type: "string", description: "From list_donations' monthlyDonors[].id." } }, required: ["templateId", "subscriptionId"] },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "adopt_monthly_donors",
|
|
54
|
+
description:
|
|
55
|
+
"Brings monthly donors over from a previous website: finds active monthly gifts on the site's connected Stripe/Paystack account that Plexo doesn't know yet and records them under a campaign, so every future charge is receipted and counted. Donors don't have to do anything. Requires the SAME payment account the old site used to be connected.",
|
|
56
|
+
inputSchema: { type: "object", properties: { templateId: { type: "string" }, campaignId: { type: "string" } }, required: ["templateId", "campaignId"] },
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const DONATION_TOOL_NAMES = new Set(DONATION_TOOL_DEFS.map((t) => t.name));
|
|
61
|
+
|
|
62
|
+
export async function handleDonationToolCall(name: string, args: any, client: PlexoClient): Promise<any> {
|
|
63
|
+
const templateId = typeof args?.templateId === "string" ? args.templateId.trim() : "";
|
|
64
|
+
if (!templateId) throw new Error("templateId is required — pass the site's home page id (see list_landing_pages).");
|
|
65
|
+
const { templateId: _t, campaignId, subscriptionId, ...fields } = args ?? {};
|
|
66
|
+
switch (name) {
|
|
67
|
+
case "list_donations":
|
|
68
|
+
return await client.listDonations(templateId);
|
|
69
|
+
|
|
70
|
+
case "create_donation_campaign": {
|
|
71
|
+
const { campaign } = await client.createDonationCampaign(templateId, fields);
|
|
72
|
+
const overview = await client.listDonations(templateId);
|
|
73
|
+
return {
|
|
74
|
+
campaign,
|
|
75
|
+
embedHtml: `<div data-plexo-donation="${campaign.id}"></div>`,
|
|
76
|
+
paymentsReady: overview.paymentsReady,
|
|
77
|
+
next: overview.paymentsReady
|
|
78
|
+
? "Add embedHtml to the page where donors should give."
|
|
79
|
+
: "Add embedHtml to a page, and ask the owner to connect Stripe/Paystack (or add bank details) in Commerce → Settings → Payments — the form shows 'being set up' until then.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case "update_donation_campaign": {
|
|
84
|
+
if (!campaignId) throw new Error("campaignId is required — use list_donations for ids.");
|
|
85
|
+
const { campaign } = await client.updateDonationCampaign(templateId, String(campaignId), fields);
|
|
86
|
+
return { updated: true, campaign };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case "cancel_monthly_donation":
|
|
90
|
+
if (!subscriptionId) throw new Error("subscriptionId is required — use list_donations' monthlyDonors[].id.");
|
|
91
|
+
return await client.cancelMonthlyDonation(templateId, String(subscriptionId));
|
|
92
|
+
|
|
93
|
+
case "adopt_monthly_donors":
|
|
94
|
+
if (!campaignId) throw new Error("campaignId is required — use list_donations for ids.");
|
|
95
|
+
return await client.adoptMonthlyDonors(templateId, String(campaignId));
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
98
|
+
}
|