@swatbloc/mcp-server 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/helpers.js +30 -0
- package/dist/tools/index.js +3 -387
- package/dist/tools/legacy-tools.js +363 -0
- package/dist/tools/sdk-tools.js +367 -0
- package/package.json +2 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SwatBloc } from "@swatbloc/sdk";
|
|
2
|
+
export function getSdkClient(args) {
|
|
3
|
+
const apiKey = args.api_key || process.env.SWATBLOC_API_KEY;
|
|
4
|
+
const baseUrl = args.base_url || process.env.SWATBLOC_BASE_URL;
|
|
5
|
+
if (!apiKey) {
|
|
6
|
+
throw new Error("Missing API key. Provide api_key in tool input or set SWATBLOC_API_KEY.");
|
|
7
|
+
}
|
|
8
|
+
return new SwatBloc(apiKey, baseUrl ? { baseUrl } : {});
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generate Cartesian product from options
|
|
12
|
+
*/
|
|
13
|
+
export function generateCartesianProduct(options) {
|
|
14
|
+
if (options.length === 0)
|
|
15
|
+
return [{}];
|
|
16
|
+
const result = [];
|
|
17
|
+
function recurse(index, current) {
|
|
18
|
+
if (index === options.length) {
|
|
19
|
+
result.push({ ...current });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const option = options[index];
|
|
23
|
+
for (const value of option.values) {
|
|
24
|
+
current[option.name] = value;
|
|
25
|
+
recurse(index + 1, current);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
recurse(0, {});
|
|
29
|
+
return result;
|
|
30
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,387 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
* Generate Cartesian product from options
|
|
5
|
-
*/
|
|
6
|
-
function generateCartesianProduct(options) {
|
|
7
|
-
if (options.length === 0)
|
|
8
|
-
return [{}];
|
|
9
|
-
const result = [];
|
|
10
|
-
function recurse(index, current) {
|
|
11
|
-
if (index === options.length) {
|
|
12
|
-
result.push({ ...current });
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
const option = options[index];
|
|
16
|
-
for (const value of option.values) {
|
|
17
|
-
current[option.name] = value;
|
|
18
|
-
recurse(index + 1, current);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
recurse(0, {});
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
export const tools = [
|
|
25
|
-
{
|
|
26
|
-
name: "create_database",
|
|
27
|
-
description: "Create a new custom database (content model) for the store. Use this to store structured data like reviews, preferences, logs, etc. This essentially upgrades the platform.",
|
|
28
|
-
schema: {
|
|
29
|
-
input: z.object({
|
|
30
|
-
store_id: z.string().describe("The ID of the store to create the database for."),
|
|
31
|
-
name: z.string().describe("Human readable name, e.g. 'Liability Photos'"),
|
|
32
|
-
slug: z.string().describe("URL friendly identifier, e.g. 'liability-photos'"),
|
|
33
|
-
fields: z.array(z.object({
|
|
34
|
-
key: z.string(),
|
|
35
|
-
type: z.enum(['text', 'number', 'boolean', 'image', 'date', 'json', 'reference']),
|
|
36
|
-
label: z.string().optional(),
|
|
37
|
-
required: z.boolean().optional()
|
|
38
|
-
})).describe("The schema definition for the database.")
|
|
39
|
-
})
|
|
40
|
-
},
|
|
41
|
-
execute: async (args) => {
|
|
42
|
-
const supabase = getSupabaseClient();
|
|
43
|
-
const { store_id, name, slug, fields } = args;
|
|
44
|
-
// Check if exists (Idempotency)
|
|
45
|
-
const { data: existing } = await supabase
|
|
46
|
-
.from('content_models')
|
|
47
|
-
.select('*')
|
|
48
|
-
.eq('store_id', store_id)
|
|
49
|
-
.eq('slug', slug)
|
|
50
|
-
.single();
|
|
51
|
-
if (existing) {
|
|
52
|
-
return {
|
|
53
|
-
message: "Database already exists",
|
|
54
|
-
model: existing
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
// Rate Limit / Safety Check: Max 50 tables per store
|
|
58
|
-
const { count } = await supabase
|
|
59
|
-
.from('content_models')
|
|
60
|
-
.select('*', { count: 'exact', head: true })
|
|
61
|
-
.eq('store_id', store_id);
|
|
62
|
-
if (count && count >= 50) {
|
|
63
|
-
return {
|
|
64
|
-
message: "Error: Maximum number of databases (50) reached for this store. Please delete some before creating more.",
|
|
65
|
-
error: true
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
const { data: newModel, error } = await supabase
|
|
69
|
-
.from('content_models')
|
|
70
|
-
.insert({
|
|
71
|
-
store_id,
|
|
72
|
-
name,
|
|
73
|
-
slug,
|
|
74
|
-
schema: { fields }
|
|
75
|
-
})
|
|
76
|
-
.select()
|
|
77
|
-
.single();
|
|
78
|
-
if (error)
|
|
79
|
-
throw new Error(error.message);
|
|
80
|
-
return {
|
|
81
|
-
message: "Database created successfully",
|
|
82
|
-
model: newModel
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
// ==================== PRODUCT TOOLS ====================
|
|
87
|
-
{
|
|
88
|
-
name: "list_products",
|
|
89
|
-
description: "List all products for a store. Returns product titles, prices, and IDs.",
|
|
90
|
-
schema: {
|
|
91
|
-
input: z.object({
|
|
92
|
-
store_id: z.string().describe("The ID of the store to list products for."),
|
|
93
|
-
limit: z.number().optional().describe("Maximum number of products to return (default: 50)"),
|
|
94
|
-
published_only: z.boolean().optional().describe("Only return published products (default: false)")
|
|
95
|
-
})
|
|
96
|
-
},
|
|
97
|
-
execute: async (args) => {
|
|
98
|
-
const supabase = getSupabaseClient();
|
|
99
|
-
const { store_id, limit = 50, published_only = false } = args;
|
|
100
|
-
let query = supabase
|
|
101
|
-
.from('products')
|
|
102
|
-
.select('id, title, slug, price, published, options')
|
|
103
|
-
.eq('store_id', store_id)
|
|
104
|
-
.order('created_at', { ascending: false })
|
|
105
|
-
.limit(limit);
|
|
106
|
-
if (published_only) {
|
|
107
|
-
query = query.eq('published', true);
|
|
108
|
-
}
|
|
109
|
-
const { data: products, error } = await query;
|
|
110
|
-
if (error)
|
|
111
|
-
throw new Error(error.message);
|
|
112
|
-
return {
|
|
113
|
-
message: `Found ${products?.length || 0} products`,
|
|
114
|
-
products: products || []
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
name: "manage_product",
|
|
120
|
-
description: "Create, update, or delete a product. For creating products with variants, first create the product with options, then use manage_variant with action 'generate' to auto-create variants.",
|
|
121
|
-
schema: {
|
|
122
|
-
input: z.object({
|
|
123
|
-
store_id: z.string().describe("The ID of the store."),
|
|
124
|
-
action: z.enum(['create', 'update', 'delete']).describe("The action to perform."),
|
|
125
|
-
product_id: z.string().optional().describe("Required for update/delete actions."),
|
|
126
|
-
data: z.object({
|
|
127
|
-
title: z.string().optional().describe("Product title"),
|
|
128
|
-
description: z.string().optional().describe("Product description"),
|
|
129
|
-
price: z.number().optional().describe("Product price in cents (e.g., 1999 = $19.99)"),
|
|
130
|
-
compare_at_price: z.number().optional().describe("Compare at price in cents"),
|
|
131
|
-
options: z.array(z.object({
|
|
132
|
-
name: z.string().describe("Option name, e.g., 'Size' or 'Color'"),
|
|
133
|
-
values: z.array(z.string()).describe("Option values, e.g., ['S', 'M', 'L']")
|
|
134
|
-
})).optional().describe("Product options for variant generation"),
|
|
135
|
-
published: z.boolean().optional().describe("Whether the product is published"),
|
|
136
|
-
category: z.string().optional().describe("Product category"),
|
|
137
|
-
sku: z.string().optional().describe("Product SKU"),
|
|
138
|
-
images: z.array(z.string()).optional().describe("Array of image URLs")
|
|
139
|
-
}).optional().describe("Product data for create/update")
|
|
140
|
-
})
|
|
141
|
-
},
|
|
142
|
-
execute: async (args) => {
|
|
143
|
-
const supabase = getSupabaseClient();
|
|
144
|
-
const { store_id, action, product_id, data } = args;
|
|
145
|
-
if (action === 'create') {
|
|
146
|
-
if (!data?.title) {
|
|
147
|
-
return { error: true, message: "Title is required to create a product" };
|
|
148
|
-
}
|
|
149
|
-
const slug = data.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') + '-' + Math.random().toString(36).substring(2, 7);
|
|
150
|
-
const productData = {
|
|
151
|
-
store_id,
|
|
152
|
-
title: data.title,
|
|
153
|
-
slug,
|
|
154
|
-
description: data.description || '',
|
|
155
|
-
price: data.price || 0,
|
|
156
|
-
compare_at_price: data.compare_at_price || null,
|
|
157
|
-
options: data.options || [],
|
|
158
|
-
published: data.published ?? false,
|
|
159
|
-
category: data.category || null,
|
|
160
|
-
sku: data.sku || null,
|
|
161
|
-
images: data.images || []
|
|
162
|
-
};
|
|
163
|
-
const { data: product, error } = await supabase
|
|
164
|
-
.from('products')
|
|
165
|
-
.insert(productData)
|
|
166
|
-
.select()
|
|
167
|
-
.single();
|
|
168
|
-
if (error)
|
|
169
|
-
throw new Error(error.message);
|
|
170
|
-
return {
|
|
171
|
-
message: "Product created successfully",
|
|
172
|
-
product
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
if (action === 'update') {
|
|
176
|
-
if (!product_id) {
|
|
177
|
-
return { error: true, message: "product_id is required for update" };
|
|
178
|
-
}
|
|
179
|
-
const updateData = {};
|
|
180
|
-
if (data?.title)
|
|
181
|
-
updateData.title = data.title;
|
|
182
|
-
if (data?.description !== undefined)
|
|
183
|
-
updateData.description = data.description;
|
|
184
|
-
if (data?.price !== undefined)
|
|
185
|
-
updateData.price = data.price;
|
|
186
|
-
if (data?.compare_at_price !== undefined)
|
|
187
|
-
updateData.compare_at_price = data.compare_at_price;
|
|
188
|
-
if (data?.options)
|
|
189
|
-
updateData.options = data.options;
|
|
190
|
-
if (data?.published !== undefined)
|
|
191
|
-
updateData.published = data.published;
|
|
192
|
-
if (data?.category !== undefined)
|
|
193
|
-
updateData.category = data.category;
|
|
194
|
-
if (data?.sku !== undefined)
|
|
195
|
-
updateData.sku = data.sku;
|
|
196
|
-
if (data?.images)
|
|
197
|
-
updateData.images = data.images;
|
|
198
|
-
const { data: product, error } = await supabase
|
|
199
|
-
.from('products')
|
|
200
|
-
.update(updateData)
|
|
201
|
-
.eq('id', product_id)
|
|
202
|
-
.eq('store_id', store_id)
|
|
203
|
-
.select()
|
|
204
|
-
.single();
|
|
205
|
-
if (error)
|
|
206
|
-
throw new Error(error.message);
|
|
207
|
-
return {
|
|
208
|
-
message: "Product updated successfully",
|
|
209
|
-
product
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (action === 'delete') {
|
|
213
|
-
if (!product_id) {
|
|
214
|
-
return { error: true, message: "product_id is required for delete" };
|
|
215
|
-
}
|
|
216
|
-
const { error } = await supabase
|
|
217
|
-
.from('products')
|
|
218
|
-
.delete()
|
|
219
|
-
.eq('id', product_id)
|
|
220
|
-
.eq('store_id', store_id);
|
|
221
|
-
if (error)
|
|
222
|
-
throw new Error(error.message);
|
|
223
|
-
return {
|
|
224
|
-
message: "Product deleted successfully"
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
return { error: true, message: "Invalid action" };
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
name: "manage_variant",
|
|
232
|
-
description: "Create, update, delete, or auto-generate variants for a product. Use 'generate' action to automatically create variants from product options (Cartesian product).",
|
|
233
|
-
schema: {
|
|
234
|
-
input: z.object({
|
|
235
|
-
product_id: z.string().describe("The ID of the product."),
|
|
236
|
-
action: z.enum(['create', 'update', 'delete', 'generate', 'list']).describe("The action to perform."),
|
|
237
|
-
variant_id: z.string().optional().describe("Required for update/delete actions."),
|
|
238
|
-
data: z.object({
|
|
239
|
-
title: z.string().optional().describe("Variant title (e.g., 'S / Red')"),
|
|
240
|
-
sku: z.string().optional().describe("Variant SKU"),
|
|
241
|
-
price: z.number().optional().describe("Variant price in cents"),
|
|
242
|
-
inventory_quantity: z.number().optional().describe("Inventory count"),
|
|
243
|
-
options: z.record(z.string(), z.string()).optional().describe("Option values, e.g., { 'Size': 'S', 'Color': 'Red' }")
|
|
244
|
-
}).optional().describe("Variant data for create/update"),
|
|
245
|
-
replace_existing: z.boolean().optional().describe("For 'generate': replace existing variants (default: true)")
|
|
246
|
-
})
|
|
247
|
-
},
|
|
248
|
-
execute: async (args) => {
|
|
249
|
-
const supabase = getSupabaseClient();
|
|
250
|
-
const { product_id, action, variant_id, data, replace_existing = true } = args;
|
|
251
|
-
// Verify product exists
|
|
252
|
-
const { data: product, error: productError } = await supabase
|
|
253
|
-
.from('products')
|
|
254
|
-
.select('id, price, options')
|
|
255
|
-
.eq('id', product_id)
|
|
256
|
-
.single();
|
|
257
|
-
if (productError || !product) {
|
|
258
|
-
return { error: true, message: "Product not found" };
|
|
259
|
-
}
|
|
260
|
-
if (action === 'list') {
|
|
261
|
-
const { data: variants, error } = await supabase
|
|
262
|
-
.from('product_variants')
|
|
263
|
-
.select('id, title, sku, price, inventory_quantity, options')
|
|
264
|
-
.eq('product_id', product_id)
|
|
265
|
-
.order('title');
|
|
266
|
-
if (error)
|
|
267
|
-
throw new Error(error.message);
|
|
268
|
-
return {
|
|
269
|
-
message: `Found ${variants?.length || 0} variants`,
|
|
270
|
-
variants: variants || []
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
if (action === 'generate') {
|
|
274
|
-
const options = product.options || [];
|
|
275
|
-
if (!options.length || !options.every((o) => o.values?.length > 0)) {
|
|
276
|
-
return {
|
|
277
|
-
error: true,
|
|
278
|
-
message: "Product must have at least one option with values defined. Update the product options first."
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
const combinations = generateCartesianProduct(options);
|
|
282
|
-
if (combinations.length > 100) {
|
|
283
|
-
return {
|
|
284
|
-
error: true,
|
|
285
|
-
message: `Too many variants (${combinations.length}). Maximum is 100. Reduce option values.`
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
if (replace_existing) {
|
|
289
|
-
await supabase
|
|
290
|
-
.from('product_variants')
|
|
291
|
-
.delete()
|
|
292
|
-
.eq('product_id', product_id);
|
|
293
|
-
}
|
|
294
|
-
const variantsToInsert = combinations.map(combo => ({
|
|
295
|
-
product_id,
|
|
296
|
-
title: Object.values(combo).join(' / '),
|
|
297
|
-
sku: null,
|
|
298
|
-
price: product.price,
|
|
299
|
-
inventory_quantity: 0,
|
|
300
|
-
options: combo,
|
|
301
|
-
description: null,
|
|
302
|
-
image_url: null,
|
|
303
|
-
images: []
|
|
304
|
-
}));
|
|
305
|
-
const { data: variants, error } = await supabase
|
|
306
|
-
.from('product_variants')
|
|
307
|
-
.insert(variantsToInsert)
|
|
308
|
-
.select();
|
|
309
|
-
if (error)
|
|
310
|
-
throw new Error(error.message);
|
|
311
|
-
return {
|
|
312
|
-
message: `Generated ${variants?.length || 0} variants from options`,
|
|
313
|
-
variants: variants || []
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
if (action === 'create') {
|
|
317
|
-
const variantData = {
|
|
318
|
-
product_id,
|
|
319
|
-
title: data?.title || 'New Variant',
|
|
320
|
-
sku: data?.sku || null,
|
|
321
|
-
price: data?.price ?? product.price,
|
|
322
|
-
inventory_quantity: data?.inventory_quantity || 0,
|
|
323
|
-
options: data?.options || {},
|
|
324
|
-
description: null,
|
|
325
|
-
image_url: null,
|
|
326
|
-
images: []
|
|
327
|
-
};
|
|
328
|
-
const { data: variant, error } = await supabase
|
|
329
|
-
.from('product_variants')
|
|
330
|
-
.insert(variantData)
|
|
331
|
-
.select()
|
|
332
|
-
.single();
|
|
333
|
-
if (error)
|
|
334
|
-
throw new Error(error.message);
|
|
335
|
-
return {
|
|
336
|
-
message: "Variant created successfully",
|
|
337
|
-
variant
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
if (action === 'update') {
|
|
341
|
-
if (!variant_id) {
|
|
342
|
-
return { error: true, message: "variant_id is required for update" };
|
|
343
|
-
}
|
|
344
|
-
const updateData = {};
|
|
345
|
-
if (data?.title)
|
|
346
|
-
updateData.title = data.title;
|
|
347
|
-
if (data?.sku !== undefined)
|
|
348
|
-
updateData.sku = data.sku;
|
|
349
|
-
if (data?.price !== undefined)
|
|
350
|
-
updateData.price = data.price;
|
|
351
|
-
if (data?.inventory_quantity !== undefined)
|
|
352
|
-
updateData.inventory_quantity = data.inventory_quantity;
|
|
353
|
-
if (data?.options)
|
|
354
|
-
updateData.options = data.options;
|
|
355
|
-
const { data: variant, error } = await supabase
|
|
356
|
-
.from('product_variants')
|
|
357
|
-
.update(updateData)
|
|
358
|
-
.eq('id', variant_id)
|
|
359
|
-
.eq('product_id', product_id)
|
|
360
|
-
.select()
|
|
361
|
-
.single();
|
|
362
|
-
if (error)
|
|
363
|
-
throw new Error(error.message);
|
|
364
|
-
return {
|
|
365
|
-
message: "Variant updated successfully",
|
|
366
|
-
variant
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
if (action === 'delete') {
|
|
370
|
-
if (!variant_id) {
|
|
371
|
-
return { error: true, message: "variant_id is required for delete" };
|
|
372
|
-
}
|
|
373
|
-
const { error } = await supabase
|
|
374
|
-
.from('product_variants')
|
|
375
|
-
.delete()
|
|
376
|
-
.eq('id', variant_id)
|
|
377
|
-
.eq('product_id', product_id);
|
|
378
|
-
if (error)
|
|
379
|
-
throw new Error(error.message);
|
|
380
|
-
return {
|
|
381
|
-
message: "Variant deleted successfully"
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
return { error: true, message: "Invalid action" };
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
];
|
|
1
|
+
import { legacyTools } from "./legacy-tools.js";
|
|
2
|
+
import { sdkTools } from "./sdk-tools.js";
|
|
3
|
+
export const tools = [...legacyTools, ...sdkTools];
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getSupabaseClient } from "../common/supabase.js";
|
|
3
|
+
import { generateCartesianProduct } from "./helpers.js";
|
|
4
|
+
export const legacyTools = [
|
|
5
|
+
{
|
|
6
|
+
name: "create_database",
|
|
7
|
+
description: "Create a new custom database (content model) for the store. Use this to store structured data like reviews, preferences, logs, etc. This essentially upgrades the platform.",
|
|
8
|
+
schema: {
|
|
9
|
+
input: z.object({
|
|
10
|
+
store_id: z.string().describe("The ID of the store to create the database for."),
|
|
11
|
+
name: z.string().describe("Human readable name, e.g. 'Liability Photos'"),
|
|
12
|
+
slug: z.string().describe("URL friendly identifier, e.g. 'liability-photos'"),
|
|
13
|
+
fields: z.array(z.object({
|
|
14
|
+
key: z.string(),
|
|
15
|
+
type: z.enum(["text", "number", "boolean", "image", "date", "json", "reference"]),
|
|
16
|
+
label: z.string().optional(),
|
|
17
|
+
required: z.boolean().optional()
|
|
18
|
+
})).describe("The schema definition for the database.")
|
|
19
|
+
})
|
|
20
|
+
},
|
|
21
|
+
execute: async (args) => {
|
|
22
|
+
const supabase = getSupabaseClient();
|
|
23
|
+
const { store_id, name, slug, fields } = args;
|
|
24
|
+
const { data: existing } = await supabase
|
|
25
|
+
.from("content_models")
|
|
26
|
+
.select("*")
|
|
27
|
+
.eq("store_id", store_id)
|
|
28
|
+
.eq("slug", slug)
|
|
29
|
+
.single();
|
|
30
|
+
if (existing) {
|
|
31
|
+
return {
|
|
32
|
+
message: "Database already exists",
|
|
33
|
+
model: existing
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const { count } = await supabase
|
|
37
|
+
.from("content_models")
|
|
38
|
+
.select("*", { count: "exact", head: true })
|
|
39
|
+
.eq("store_id", store_id);
|
|
40
|
+
if (count && count >= 50) {
|
|
41
|
+
return {
|
|
42
|
+
message: "Error: Maximum number of databases (50) reached for this store. Please delete some before creating more.",
|
|
43
|
+
error: true
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const { data: newModel, error } = await supabase
|
|
47
|
+
.from("content_models")
|
|
48
|
+
.insert({
|
|
49
|
+
store_id,
|
|
50
|
+
name,
|
|
51
|
+
slug,
|
|
52
|
+
schema: { fields }
|
|
53
|
+
})
|
|
54
|
+
.select()
|
|
55
|
+
.single();
|
|
56
|
+
if (error)
|
|
57
|
+
throw new Error(error.message);
|
|
58
|
+
return {
|
|
59
|
+
message: "Database created successfully",
|
|
60
|
+
model: newModel
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "list_products",
|
|
66
|
+
description: "List all products for a store. Returns product titles, prices, and IDs.",
|
|
67
|
+
schema: {
|
|
68
|
+
input: z.object({
|
|
69
|
+
store_id: z.string().describe("The ID of the store to list products for."),
|
|
70
|
+
limit: z.number().optional().describe("Maximum number of products to return (default: 50)"),
|
|
71
|
+
published_only: z.boolean().optional().describe("Only return published products (default: false)")
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
execute: async (args) => {
|
|
75
|
+
const supabase = getSupabaseClient();
|
|
76
|
+
const { store_id, limit = 50, published_only = false } = args;
|
|
77
|
+
let query = supabase
|
|
78
|
+
.from("products")
|
|
79
|
+
.select("id, title, slug, price, published, options")
|
|
80
|
+
.eq("store_id", store_id)
|
|
81
|
+
.order("created_at", { ascending: false })
|
|
82
|
+
.limit(limit);
|
|
83
|
+
if (published_only) {
|
|
84
|
+
query = query.eq("published", true);
|
|
85
|
+
}
|
|
86
|
+
const { data: products, error } = await query;
|
|
87
|
+
if (error)
|
|
88
|
+
throw new Error(error.message);
|
|
89
|
+
return {
|
|
90
|
+
message: `Found ${products?.length || 0} products`,
|
|
91
|
+
products: products || []
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "manage_product",
|
|
97
|
+
description: "Create, update, or delete a product. For creating products with variants, first create the product with options, then use manage_variant with action 'generate' to auto-create variants.",
|
|
98
|
+
schema: {
|
|
99
|
+
input: z.object({
|
|
100
|
+
store_id: z.string().describe("The ID of the store."),
|
|
101
|
+
action: z.enum(["create", "update", "delete"]).describe("The action to perform."),
|
|
102
|
+
product_id: z.string().optional().describe("Required for update/delete actions."),
|
|
103
|
+
data: z.object({
|
|
104
|
+
title: z.string().optional().describe("Product title"),
|
|
105
|
+
description: z.string().optional().describe("Product description"),
|
|
106
|
+
price: z.number().optional().describe("Product price in cents (e.g., 1999 = $19.99)"),
|
|
107
|
+
compare_at_price: z.number().optional().describe("Compare at price in cents"),
|
|
108
|
+
options: z.array(z.object({
|
|
109
|
+
name: z.string().describe("Option name, e.g., 'Size' or 'Color'"),
|
|
110
|
+
values: z.array(z.string()).describe("Option values, e.g., ['S', 'M', 'L']")
|
|
111
|
+
})).optional().describe("Product options for variant generation"),
|
|
112
|
+
published: z.boolean().optional().describe("Whether the product is published"),
|
|
113
|
+
category: z.string().optional().describe("Product category"),
|
|
114
|
+
sku: z.string().optional().describe("Product SKU"),
|
|
115
|
+
images: z.array(z.string()).optional().describe("Array of image URLs")
|
|
116
|
+
}).optional().describe("Product data for create/update")
|
|
117
|
+
})
|
|
118
|
+
},
|
|
119
|
+
execute: async (args) => {
|
|
120
|
+
const supabase = getSupabaseClient();
|
|
121
|
+
const { store_id, action, product_id, data } = args;
|
|
122
|
+
if (action === "create") {
|
|
123
|
+
if (!data?.title) {
|
|
124
|
+
return { error: true, message: "Title is required to create a product" };
|
|
125
|
+
}
|
|
126
|
+
const slug = data.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") + "-" + Math.random().toString(36).substring(2, 7);
|
|
127
|
+
const productData = {
|
|
128
|
+
store_id,
|
|
129
|
+
title: data.title,
|
|
130
|
+
slug,
|
|
131
|
+
description: data.description || "",
|
|
132
|
+
price: data.price || 0,
|
|
133
|
+
compare_at_price: data.compare_at_price || null,
|
|
134
|
+
options: data.options || [],
|
|
135
|
+
published: data.published ?? false,
|
|
136
|
+
category: data.category || null,
|
|
137
|
+
sku: data.sku || null,
|
|
138
|
+
images: data.images || []
|
|
139
|
+
};
|
|
140
|
+
const { data: product, error } = await supabase
|
|
141
|
+
.from("products")
|
|
142
|
+
.insert(productData)
|
|
143
|
+
.select()
|
|
144
|
+
.single();
|
|
145
|
+
if (error)
|
|
146
|
+
throw new Error(error.message);
|
|
147
|
+
return {
|
|
148
|
+
message: "Product created successfully",
|
|
149
|
+
product
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (action === "update") {
|
|
153
|
+
if (!product_id) {
|
|
154
|
+
return { error: true, message: "product_id is required for update" };
|
|
155
|
+
}
|
|
156
|
+
const updateData = {};
|
|
157
|
+
if (data?.title)
|
|
158
|
+
updateData.title = data.title;
|
|
159
|
+
if (data?.description !== undefined)
|
|
160
|
+
updateData.description = data.description;
|
|
161
|
+
if (data?.price !== undefined)
|
|
162
|
+
updateData.price = data.price;
|
|
163
|
+
if (data?.compare_at_price !== undefined)
|
|
164
|
+
updateData.compare_at_price = data.compare_at_price;
|
|
165
|
+
if (data?.options)
|
|
166
|
+
updateData.options = data.options;
|
|
167
|
+
if (data?.published !== undefined)
|
|
168
|
+
updateData.published = data.published;
|
|
169
|
+
if (data?.category !== undefined)
|
|
170
|
+
updateData.category = data.category;
|
|
171
|
+
if (data?.sku !== undefined)
|
|
172
|
+
updateData.sku = data.sku;
|
|
173
|
+
if (data?.images)
|
|
174
|
+
updateData.images = data.images;
|
|
175
|
+
const { data: product, error } = await supabase
|
|
176
|
+
.from("products")
|
|
177
|
+
.update(updateData)
|
|
178
|
+
.eq("id", product_id)
|
|
179
|
+
.eq("store_id", store_id)
|
|
180
|
+
.select()
|
|
181
|
+
.single();
|
|
182
|
+
if (error)
|
|
183
|
+
throw new Error(error.message);
|
|
184
|
+
return {
|
|
185
|
+
message: "Product updated successfully",
|
|
186
|
+
product
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (action === "delete") {
|
|
190
|
+
if (!product_id) {
|
|
191
|
+
return { error: true, message: "product_id is required for delete" };
|
|
192
|
+
}
|
|
193
|
+
const { error } = await supabase
|
|
194
|
+
.from("products")
|
|
195
|
+
.delete()
|
|
196
|
+
.eq("id", product_id)
|
|
197
|
+
.eq("store_id", store_id);
|
|
198
|
+
if (error)
|
|
199
|
+
throw new Error(error.message);
|
|
200
|
+
return {
|
|
201
|
+
message: "Product deleted successfully"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return { error: true, message: "Invalid action" };
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "manage_variant",
|
|
209
|
+
description: "Create, update, delete, or auto-generate variants for a product. Use 'generate' action to automatically create variants from product options (Cartesian product).",
|
|
210
|
+
schema: {
|
|
211
|
+
input: z.object({
|
|
212
|
+
product_id: z.string().describe("The ID of the product."),
|
|
213
|
+
action: z.enum(["create", "update", "delete", "generate", "list"]).describe("The action to perform."),
|
|
214
|
+
variant_id: z.string().optional().describe("Required for update/delete actions."),
|
|
215
|
+
data: z.object({
|
|
216
|
+
title: z.string().optional().describe("Variant title (e.g., 'S / Red')"),
|
|
217
|
+
sku: z.string().optional().describe("Variant SKU"),
|
|
218
|
+
price: z.number().optional().describe("Variant price in cents"),
|
|
219
|
+
inventory_quantity: z.number().optional().describe("Inventory count"),
|
|
220
|
+
options: z.record(z.string(), z.string()).optional().describe("Option values, e.g., { 'Size': 'S', 'Color': 'Red' }")
|
|
221
|
+
}).optional().describe("Variant data for create/update"),
|
|
222
|
+
replace_existing: z.boolean().optional().describe("For 'generate': replace existing variants (default: true)")
|
|
223
|
+
})
|
|
224
|
+
},
|
|
225
|
+
execute: async (args) => {
|
|
226
|
+
const supabase = getSupabaseClient();
|
|
227
|
+
const { product_id, action, variant_id, data, replace_existing = true } = args;
|
|
228
|
+
const { data: product, error: productError } = await supabase
|
|
229
|
+
.from("products")
|
|
230
|
+
.select("id, price, options")
|
|
231
|
+
.eq("id", product_id)
|
|
232
|
+
.single();
|
|
233
|
+
if (productError || !product) {
|
|
234
|
+
return { error: true, message: "Product not found" };
|
|
235
|
+
}
|
|
236
|
+
if (action === "list") {
|
|
237
|
+
const { data: variants, error } = await supabase
|
|
238
|
+
.from("product_variants")
|
|
239
|
+
.select("id, title, sku, price, inventory_quantity, options")
|
|
240
|
+
.eq("product_id", product_id)
|
|
241
|
+
.order("title");
|
|
242
|
+
if (error)
|
|
243
|
+
throw new Error(error.message);
|
|
244
|
+
return {
|
|
245
|
+
message: `Found ${variants?.length || 0} variants`,
|
|
246
|
+
variants: variants || []
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (action === "generate") {
|
|
250
|
+
const options = product.options || [];
|
|
251
|
+
if (!options.length || !options.every((o) => o.values?.length > 0)) {
|
|
252
|
+
return {
|
|
253
|
+
error: true,
|
|
254
|
+
message: "Product must have at least one option with values defined. Update the product options first."
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const combinations = generateCartesianProduct(options);
|
|
258
|
+
if (combinations.length > 100) {
|
|
259
|
+
return {
|
|
260
|
+
error: true,
|
|
261
|
+
message: `Too many variants (${combinations.length}). Maximum is 100. Reduce option values.`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (replace_existing) {
|
|
265
|
+
await supabase
|
|
266
|
+
.from("product_variants")
|
|
267
|
+
.delete()
|
|
268
|
+
.eq("product_id", product_id);
|
|
269
|
+
}
|
|
270
|
+
const variantsToInsert = combinations.map(combo => ({
|
|
271
|
+
product_id,
|
|
272
|
+
title: Object.values(combo).join(" / "),
|
|
273
|
+
sku: null,
|
|
274
|
+
price: product.price,
|
|
275
|
+
inventory_quantity: 0,
|
|
276
|
+
options: combo,
|
|
277
|
+
description: null,
|
|
278
|
+
image_url: null,
|
|
279
|
+
images: []
|
|
280
|
+
}));
|
|
281
|
+
const { data: variants, error } = await supabase
|
|
282
|
+
.from("product_variants")
|
|
283
|
+
.insert(variantsToInsert)
|
|
284
|
+
.select();
|
|
285
|
+
if (error)
|
|
286
|
+
throw new Error(error.message);
|
|
287
|
+
return {
|
|
288
|
+
message: `Generated ${variants?.length || 0} variants from options`,
|
|
289
|
+
variants: variants || []
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
if (action === "create") {
|
|
293
|
+
const variantData = {
|
|
294
|
+
product_id,
|
|
295
|
+
title: data?.title || "New Variant",
|
|
296
|
+
sku: data?.sku || null,
|
|
297
|
+
price: data?.price ?? product.price,
|
|
298
|
+
inventory_quantity: data?.inventory_quantity || 0,
|
|
299
|
+
options: data?.options || {},
|
|
300
|
+
description: null,
|
|
301
|
+
image_url: null,
|
|
302
|
+
images: []
|
|
303
|
+
};
|
|
304
|
+
const { data: variant, error } = await supabase
|
|
305
|
+
.from("product_variants")
|
|
306
|
+
.insert(variantData)
|
|
307
|
+
.select()
|
|
308
|
+
.single();
|
|
309
|
+
if (error)
|
|
310
|
+
throw new Error(error.message);
|
|
311
|
+
return {
|
|
312
|
+
message: "Variant created successfully",
|
|
313
|
+
variant
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (action === "update") {
|
|
317
|
+
if (!variant_id) {
|
|
318
|
+
return { error: true, message: "variant_id is required for update" };
|
|
319
|
+
}
|
|
320
|
+
const updateData = {};
|
|
321
|
+
if (data?.title)
|
|
322
|
+
updateData.title = data.title;
|
|
323
|
+
if (data?.sku !== undefined)
|
|
324
|
+
updateData.sku = data.sku;
|
|
325
|
+
if (data?.price !== undefined)
|
|
326
|
+
updateData.price = data.price;
|
|
327
|
+
if (data?.inventory_quantity !== undefined)
|
|
328
|
+
updateData.inventory_quantity = data.inventory_quantity;
|
|
329
|
+
if (data?.options)
|
|
330
|
+
updateData.options = data.options;
|
|
331
|
+
const { data: variant, error } = await supabase
|
|
332
|
+
.from("product_variants")
|
|
333
|
+
.update(updateData)
|
|
334
|
+
.eq("id", variant_id)
|
|
335
|
+
.eq("product_id", product_id)
|
|
336
|
+
.select()
|
|
337
|
+
.single();
|
|
338
|
+
if (error)
|
|
339
|
+
throw new Error(error.message);
|
|
340
|
+
return {
|
|
341
|
+
message: "Variant updated successfully",
|
|
342
|
+
variant
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (action === "delete") {
|
|
346
|
+
if (!variant_id) {
|
|
347
|
+
return { error: true, message: "variant_id is required for delete" };
|
|
348
|
+
}
|
|
349
|
+
const { error } = await supabase
|
|
350
|
+
.from("product_variants")
|
|
351
|
+
.delete()
|
|
352
|
+
.eq("id", variant_id)
|
|
353
|
+
.eq("product_id", product_id);
|
|
354
|
+
if (error)
|
|
355
|
+
throw new Error(error.message);
|
|
356
|
+
return {
|
|
357
|
+
message: "Variant deleted successfully"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return { error: true, message: "Invalid action" };
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
];
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getSdkClient } from "./helpers.js";
|
|
3
|
+
export const sdkTools = [
|
|
4
|
+
{
|
|
5
|
+
name: "sdk_products",
|
|
6
|
+
description: "Full Products SDK tool: list, get, create, update, delete, by_category, search, update_pipeline.",
|
|
7
|
+
schema: {
|
|
8
|
+
input: z.object({
|
|
9
|
+
api_key: z.string().optional().describe("SwatBloc API key (pk_... or sk_...). Falls back to SWATBLOC_API_KEY env."),
|
|
10
|
+
base_url: z.string().url().optional().describe("Optional API base URL. Falls back to SWATBLOC_BASE_URL env."),
|
|
11
|
+
action: z.enum(["list", "get", "create", "update", "delete", "by_category", "search", "update_pipeline"]),
|
|
12
|
+
id: z.string().optional(),
|
|
13
|
+
id_or_slug: z.string().optional(),
|
|
14
|
+
data: z.record(z.string(), z.any()).optional(),
|
|
15
|
+
category: z.string().optional(),
|
|
16
|
+
query: z.string().optional(),
|
|
17
|
+
options: z.object({
|
|
18
|
+
limit: z.number().optional(),
|
|
19
|
+
offset: z.number().optional(),
|
|
20
|
+
category: z.string().optional(),
|
|
21
|
+
search: z.string().optional()
|
|
22
|
+
}).optional(),
|
|
23
|
+
pipeline_steps: z.array(z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
label: z.string(),
|
|
26
|
+
required_metadata: z.array(z.string()).default([])
|
|
27
|
+
})).optional()
|
|
28
|
+
})
|
|
29
|
+
},
|
|
30
|
+
execute: async (args) => {
|
|
31
|
+
const sdk = getSdkClient(args);
|
|
32
|
+
switch (args.action) {
|
|
33
|
+
case "list":
|
|
34
|
+
return sdk.products.list(args.options || {});
|
|
35
|
+
case "get":
|
|
36
|
+
if (!args.id_or_slug)
|
|
37
|
+
throw new Error("id_or_slug is required for get");
|
|
38
|
+
return sdk.products.get(args.id_or_slug);
|
|
39
|
+
case "create":
|
|
40
|
+
if (!args.data)
|
|
41
|
+
throw new Error("data is required for create");
|
|
42
|
+
return sdk.products.create(args.data);
|
|
43
|
+
case "update":
|
|
44
|
+
if (!args.id)
|
|
45
|
+
throw new Error("id is required for update");
|
|
46
|
+
if (!args.data)
|
|
47
|
+
throw new Error("data is required for update");
|
|
48
|
+
return sdk.products.update(args.id, args.data);
|
|
49
|
+
case "delete":
|
|
50
|
+
if (!args.id)
|
|
51
|
+
throw new Error("id is required for delete");
|
|
52
|
+
await sdk.products.delete(args.id);
|
|
53
|
+
return { message: "Product deleted" };
|
|
54
|
+
case "by_category":
|
|
55
|
+
if (!args.category)
|
|
56
|
+
throw new Error("category is required for by_category");
|
|
57
|
+
return sdk.products.byCategory(args.category, args.options || {});
|
|
58
|
+
case "search":
|
|
59
|
+
if (!args.query)
|
|
60
|
+
throw new Error("query is required for search");
|
|
61
|
+
return sdk.products.search(args.query, args.options || {});
|
|
62
|
+
case "update_pipeline":
|
|
63
|
+
if (!args.id)
|
|
64
|
+
throw new Error("id is required for update_pipeline");
|
|
65
|
+
if (!args.pipeline_steps)
|
|
66
|
+
throw new Error("pipeline_steps is required for update_pipeline");
|
|
67
|
+
if (typeof sdk.products.updatePipeline === "function") {
|
|
68
|
+
return sdk.products.updatePipeline(args.id, args.pipeline_steps);
|
|
69
|
+
}
|
|
70
|
+
return sdk.products.update(args.id, { fulfillment_pipeline: args.pipeline_steps });
|
|
71
|
+
default:
|
|
72
|
+
return { error: true, message: "Invalid action" };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "sdk_variants",
|
|
78
|
+
description: "Full Variants SDK tool: list, get, create, update, delete, generate.",
|
|
79
|
+
schema: {
|
|
80
|
+
input: z.object({
|
|
81
|
+
api_key: z.string().optional(),
|
|
82
|
+
base_url: z.string().url().optional(),
|
|
83
|
+
action: z.enum(["list", "get", "create", "update", "delete", "generate"]),
|
|
84
|
+
product_id: z.string(),
|
|
85
|
+
variant_id: z.string().optional(),
|
|
86
|
+
data: z.record(z.string(), z.any()).optional(),
|
|
87
|
+
replace_existing: z.boolean().optional()
|
|
88
|
+
})
|
|
89
|
+
},
|
|
90
|
+
execute: async (args) => {
|
|
91
|
+
const sdk = getSdkClient(args);
|
|
92
|
+
switch (args.action) {
|
|
93
|
+
case "list":
|
|
94
|
+
return sdk.variants.list(args.product_id);
|
|
95
|
+
case "get":
|
|
96
|
+
if (!args.variant_id)
|
|
97
|
+
throw new Error("variant_id is required for get");
|
|
98
|
+
return sdk.variants.get(args.product_id, args.variant_id);
|
|
99
|
+
case "create":
|
|
100
|
+
if (!args.data)
|
|
101
|
+
throw new Error("data is required for create");
|
|
102
|
+
return sdk.variants.create(args.product_id, args.data);
|
|
103
|
+
case "update":
|
|
104
|
+
if (!args.variant_id)
|
|
105
|
+
throw new Error("variant_id is required for update");
|
|
106
|
+
if (!args.data)
|
|
107
|
+
throw new Error("data is required for update");
|
|
108
|
+
return sdk.variants.update(args.product_id, args.variant_id, args.data);
|
|
109
|
+
case "delete":
|
|
110
|
+
if (!args.variant_id)
|
|
111
|
+
throw new Error("variant_id is required for delete");
|
|
112
|
+
await sdk.variants.delete(args.product_id, args.variant_id);
|
|
113
|
+
return { message: "Variant deleted" };
|
|
114
|
+
case "generate":
|
|
115
|
+
return sdk.variants.generateFromOptions(args.product_id, args.replace_existing ?? true);
|
|
116
|
+
default:
|
|
117
|
+
return { error: true, message: "Invalid action" };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "sdk_cart",
|
|
123
|
+
description: "Full Cart SDK tool: create, get, add_items, update_item, remove_item.",
|
|
124
|
+
schema: {
|
|
125
|
+
input: z.object({
|
|
126
|
+
api_key: z.string().optional(),
|
|
127
|
+
base_url: z.string().url().optional(),
|
|
128
|
+
action: z.enum(["create", "get", "add_items", "update_item", "remove_item"]),
|
|
129
|
+
cart_id: z.string().optional(),
|
|
130
|
+
items: z.array(z.object({
|
|
131
|
+
productId: z.string(),
|
|
132
|
+
quantity: z.number(),
|
|
133
|
+
variantId: z.string().optional(),
|
|
134
|
+
metadata: z.record(z.string(), z.any()).optional()
|
|
135
|
+
})).optional(),
|
|
136
|
+
product_id: z.string().optional(),
|
|
137
|
+
quantity: z.number().optional()
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
execute: async (args) => {
|
|
141
|
+
const sdk = getSdkClient(args);
|
|
142
|
+
switch (args.action) {
|
|
143
|
+
case "create":
|
|
144
|
+
if (!args.items)
|
|
145
|
+
throw new Error("items is required for create");
|
|
146
|
+
return sdk.cart.create(args.items);
|
|
147
|
+
case "get":
|
|
148
|
+
if (!args.cart_id)
|
|
149
|
+
throw new Error("cart_id is required for get");
|
|
150
|
+
return sdk.cart.get(args.cart_id);
|
|
151
|
+
case "add_items":
|
|
152
|
+
if (!args.cart_id)
|
|
153
|
+
throw new Error("cart_id is required for add_items");
|
|
154
|
+
if (!args.items)
|
|
155
|
+
throw new Error("items is required for add_items");
|
|
156
|
+
return sdk.cart.addItems(args.cart_id, args.items);
|
|
157
|
+
case "update_item":
|
|
158
|
+
if (!args.cart_id)
|
|
159
|
+
throw new Error("cart_id is required for update_item");
|
|
160
|
+
if (!args.product_id)
|
|
161
|
+
throw new Error("product_id is required for update_item");
|
|
162
|
+
if (args.quantity === undefined)
|
|
163
|
+
throw new Error("quantity is required for update_item");
|
|
164
|
+
return sdk.cart.updateItem(args.cart_id, args.product_id, args.quantity);
|
|
165
|
+
case "remove_item":
|
|
166
|
+
if (!args.cart_id)
|
|
167
|
+
throw new Error("cart_id is required for remove_item");
|
|
168
|
+
if (!args.product_id)
|
|
169
|
+
throw new Error("product_id is required for remove_item");
|
|
170
|
+
return sdk.cart.removeItem(args.cart_id, args.product_id);
|
|
171
|
+
default:
|
|
172
|
+
return { error: true, message: "Invalid action" };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "sdk_checkout",
|
|
178
|
+
description: "Checkout SDK tool: create checkout session from cart.",
|
|
179
|
+
schema: {
|
|
180
|
+
input: z.object({
|
|
181
|
+
api_key: z.string().optional(),
|
|
182
|
+
base_url: z.string().url().optional(),
|
|
183
|
+
cart_id: z.string(),
|
|
184
|
+
success_url: z.string().url(),
|
|
185
|
+
cancel_url: z.string().url()
|
|
186
|
+
})
|
|
187
|
+
},
|
|
188
|
+
execute: async (args) => {
|
|
189
|
+
const sdk = getSdkClient(args);
|
|
190
|
+
return sdk.checkout.create(args.cart_id, {
|
|
191
|
+
successUrl: args.success_url,
|
|
192
|
+
cancelUrl: args.cancel_url
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "sdk_store",
|
|
198
|
+
description: "Store SDK tool: fetch store info.",
|
|
199
|
+
schema: {
|
|
200
|
+
input: z.object({
|
|
201
|
+
api_key: z.string().optional(),
|
|
202
|
+
base_url: z.string().url().optional()
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
execute: async (args) => {
|
|
206
|
+
const sdk = getSdkClient(args);
|
|
207
|
+
return sdk.store.info();
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "sdk_media",
|
|
212
|
+
description: "Media SDK tool: list content library assets.",
|
|
213
|
+
schema: {
|
|
214
|
+
input: z.object({
|
|
215
|
+
api_key: z.string().optional(),
|
|
216
|
+
base_url: z.string().url().optional(),
|
|
217
|
+
search: z.string().optional(),
|
|
218
|
+
limit: z.number().optional(),
|
|
219
|
+
offset: z.number().optional()
|
|
220
|
+
})
|
|
221
|
+
},
|
|
222
|
+
execute: async (args) => {
|
|
223
|
+
const sdk = getSdkClient(args);
|
|
224
|
+
return sdk.media.list({
|
|
225
|
+
search: args.search,
|
|
226
|
+
limit: args.limit,
|
|
227
|
+
offset: args.offset
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "sdk_models",
|
|
233
|
+
description: "Custom DB model SDK tool: list_models or create_model.",
|
|
234
|
+
schema: {
|
|
235
|
+
input: z.object({
|
|
236
|
+
api_key: z.string().optional(),
|
|
237
|
+
base_url: z.string().url().optional(),
|
|
238
|
+
action: z.enum(["list_models", "create_model"]),
|
|
239
|
+
name: z.string().optional(),
|
|
240
|
+
slug: z.string().optional(),
|
|
241
|
+
schema: z.object({
|
|
242
|
+
fields: z.array(z.object({
|
|
243
|
+
key: z.string(),
|
|
244
|
+
type: z.enum(["text", "number", "boolean", "image", "date", "json", "reference"]),
|
|
245
|
+
label: z.string().optional(),
|
|
246
|
+
required: z.boolean().optional()
|
|
247
|
+
}))
|
|
248
|
+
}).optional()
|
|
249
|
+
})
|
|
250
|
+
},
|
|
251
|
+
execute: async (args) => {
|
|
252
|
+
const sdk = getSdkClient(args);
|
|
253
|
+
if (args.action === "list_models") {
|
|
254
|
+
return sdk.db.listModels();
|
|
255
|
+
}
|
|
256
|
+
if (!args.name || !args.slug || !args.schema) {
|
|
257
|
+
throw new Error("name, slug, and schema are required for create_model");
|
|
258
|
+
}
|
|
259
|
+
return sdk.db.createModel(args.name, args.slug, args.schema);
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "sdk_content",
|
|
264
|
+
description: "Custom DB content SDK tool: list, get, create, update, delete items for a collection slug.",
|
|
265
|
+
schema: {
|
|
266
|
+
input: z.object({
|
|
267
|
+
api_key: z.string().optional(),
|
|
268
|
+
base_url: z.string().url().optional(),
|
|
269
|
+
action: z.enum(["list", "get", "create", "update", "delete"]),
|
|
270
|
+
slug: z.string(),
|
|
271
|
+
id: z.string().optional(),
|
|
272
|
+
data: z.record(z.string(), z.any()).optional(),
|
|
273
|
+
options: z.object({
|
|
274
|
+
limit: z.number().optional(),
|
|
275
|
+
offset: z.number().optional(),
|
|
276
|
+
sort: z.string().optional(),
|
|
277
|
+
filter: z.record(z.string(), z.any()).optional()
|
|
278
|
+
}).optional()
|
|
279
|
+
})
|
|
280
|
+
},
|
|
281
|
+
execute: async (args) => {
|
|
282
|
+
const sdk = getSdkClient(args);
|
|
283
|
+
const collection = sdk.db.collection(args.slug);
|
|
284
|
+
switch (args.action) {
|
|
285
|
+
case "list":
|
|
286
|
+
return collection.list(args.options || {});
|
|
287
|
+
case "get":
|
|
288
|
+
if (!args.id)
|
|
289
|
+
throw new Error("id is required for get");
|
|
290
|
+
return collection.get(args.id);
|
|
291
|
+
case "create":
|
|
292
|
+
if (!args.data)
|
|
293
|
+
throw new Error("data is required for create");
|
|
294
|
+
return collection.create(args.data);
|
|
295
|
+
case "update":
|
|
296
|
+
if (!args.id)
|
|
297
|
+
throw new Error("id is required for update");
|
|
298
|
+
if (!args.data)
|
|
299
|
+
throw new Error("data is required for update");
|
|
300
|
+
return collection.update(args.id, args.data);
|
|
301
|
+
case "delete":
|
|
302
|
+
if (!args.id)
|
|
303
|
+
throw new Error("id is required for delete");
|
|
304
|
+
await collection.delete(args.id);
|
|
305
|
+
return { message: "Content item deleted" };
|
|
306
|
+
default:
|
|
307
|
+
return { error: true, message: "Invalid action" };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "sdk_orders",
|
|
313
|
+
description: "Orders SDK tool: list, get, update, transition_item.",
|
|
314
|
+
schema: {
|
|
315
|
+
input: z.object({
|
|
316
|
+
api_key: z.string().optional().describe("Secret API key recommended for admin operations."),
|
|
317
|
+
base_url: z.string().url().optional(),
|
|
318
|
+
action: z.enum(["list", "get", "update", "transition_item"]),
|
|
319
|
+
id: z.string().optional(),
|
|
320
|
+
options: z.object({
|
|
321
|
+
limit: z.number().optional(),
|
|
322
|
+
offset: z.number().optional(),
|
|
323
|
+
status: z.string().optional(),
|
|
324
|
+
sessionId: z.string().optional()
|
|
325
|
+
}).optional(),
|
|
326
|
+
updates: z.record(z.string(), z.any()).optional(),
|
|
327
|
+
order_id: z.string().optional(),
|
|
328
|
+
item_id: z.string().optional(),
|
|
329
|
+
transition: z.object({
|
|
330
|
+
stepId: z.string(),
|
|
331
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
332
|
+
status: z.enum(["processing", "completed", "cancelled"]).optional()
|
|
333
|
+
}).optional()
|
|
334
|
+
})
|
|
335
|
+
},
|
|
336
|
+
execute: async (args) => {
|
|
337
|
+
const sdk = getSdkClient(args);
|
|
338
|
+
switch (args.action) {
|
|
339
|
+
case "list":
|
|
340
|
+
return sdk.orders.list(args.options || {});
|
|
341
|
+
case "get":
|
|
342
|
+
if (!args.id)
|
|
343
|
+
throw new Error("id is required for get");
|
|
344
|
+
return sdk.orders.get(args.id);
|
|
345
|
+
case "update":
|
|
346
|
+
if (!args.id)
|
|
347
|
+
throw new Error("id is required for update");
|
|
348
|
+
if (!args.updates)
|
|
349
|
+
throw new Error("updates is required for update");
|
|
350
|
+
return sdk.orders.update(args.id, args.updates);
|
|
351
|
+
case "transition_item":
|
|
352
|
+
if (!args.order_id)
|
|
353
|
+
throw new Error("order_id is required for transition_item");
|
|
354
|
+
if (!args.item_id)
|
|
355
|
+
throw new Error("item_id is required for transition_item");
|
|
356
|
+
if (!args.transition)
|
|
357
|
+
throw new Error("transition is required for transition_item");
|
|
358
|
+
if (typeof sdk.orders.transitionItem === "function") {
|
|
359
|
+
return sdk.orders.transitionItem(args.order_id, args.item_id, args.transition);
|
|
360
|
+
}
|
|
361
|
+
throw new Error("transition_item requires a newer @swatbloc/sdk version that includes orders.transitionItem().");
|
|
362
|
+
default:
|
|
363
|
+
return { error: true, message: "Invalid action" };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swatbloc/mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"prepublishOnly": "npm run build"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@swatbloc/sdk": "^1.4.4",
|
|
23
24
|
"@supabase/supabase-js": "^2.86.2",
|
|
24
25
|
"@modelcontextprotocol/sdk": "^0.6.0",
|
|
25
26
|
"zod": "^4.3.5",
|