@swatbloc/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # MegaPlatform MCP Server
2
+
3
+ This package provides a Model Context Protocol (MCP) server for the MegaPlatform monorepo.
4
+
5
+ ## Features
6
+ - **Project Rules**: Provides project rules and guidelines to AI agents via the `project-rules` prompt.
7
+
8
+ ## Usage
9
+ To use this with your AI assistant (e.g., Claude Desktop, Cursor, etc.), configure it as a local MCP server.
10
+
11
+ ### Configuration
12
+ **Command:** `node`
13
+ **Args:** `[absolute-path-to-repo]/packages/mcp-server/dist/index.js`
14
+
15
+ ### Development
16
+ - `npm install`: Install dependencies
17
+ - `npm run build`: Build the server
18
+ - `npm run dev`: Watch mode
19
+
20
+ :)
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "./server.js";
3
+ const server = new McpServer();
4
+ server.run().catch((error) => {
5
+ console.error("Fatal error in main():", error);
6
+ process.exit(1);
7
+ });
@@ -0,0 +1,4 @@
1
+ import { projectRulesPrompt } from "./rules.js";
2
+ export const prompts = [
3
+ projectRulesPrompt
4
+ ];
@@ -0,0 +1,124 @@
1
+ const PROJECT_RULES_CONTENT = `
2
+ # SwatBloc SDK - Cursor Rules
3
+
4
+ You are building an app that uses the SwatBloc SDK for headless commerce.
5
+
6
+ ## Installation
7
+
8
+ \`\`\`bash
9
+ npm install @swatbloc/sdk
10
+ \`\`\`
11
+
12
+ ## Initialization
13
+
14
+ \`\`\`typescript
15
+ import { SwatBloc } from '@swatbloc/sdk';
16
+
17
+ // Initialize with your public API key from SwatBloc dashboard
18
+ const swat = new SwatBloc('pk_live_YOUR_KEY_HERE');
19
+ \`\`\`
20
+
21
+ ## API Reference
22
+
23
+ ### Products
24
+
25
+ \`\`\`typescript
26
+ // List all products
27
+ const products = await swat.products.list();
28
+
29
+ // List with options
30
+ const products = await swat.products.list({
31
+ limit: 10,
32
+ offset: 0,
33
+ category: 'shoes',
34
+ search: 'running'
35
+ });
36
+
37
+ // Get single product by ID or slug
38
+ const product = await swat.products.get('prod_123');
39
+ const product = await swat.products.get('blue-running-shoes');
40
+
41
+ // Get by category
42
+ const shoes = await swat.products.byCategory('shoes');
43
+
44
+ // Search products
45
+ const results = await swat.products.search('running shoes');
46
+ \`\`\`
47
+
48
+ ### Cart
49
+
50
+ \`\`\`typescript
51
+ // Create a cart
52
+ const cart = await swat.cart.create([
53
+ { productId: 'prod_123', quantity: 2 },
54
+ { productId: 'prod_456', quantity: 1 }
55
+ ]);
56
+
57
+ // Get existing cart
58
+ const cart = await swat.cart.get('cart_abc123');
59
+
60
+ // Add items
61
+ const cart = await swat.cart.addItems('cart_abc123', [
62
+ { productId: 'prod_789', quantity: 1 }
63
+ ]);
64
+
65
+ // Update quantity
66
+ const cart = await swat.cart.updateItem('cart_abc123', 'prod_123', 3);
67
+
68
+ // Remove item
69
+ const cart = await swat.cart.removeItem('cart_abc123', 'prod_123');
70
+ \`\`\`
71
+
72
+ ### Checkout
73
+
74
+ \`\`\`typescript
75
+ // Create checkout session (redirects to Stripe)
76
+ const checkout = await swat.checkout.create('cart_abc123', {
77
+ successUrl: 'https://mysite.com/success',
78
+ cancelUrl: 'https://mysite.com/cart'
79
+ });
80
+
81
+ // Redirect user to checkout
82
+ window.location.href = checkout.url;
83
+ \`\`\`
84
+
85
+ ### Store Info
86
+
87
+ \`\`\`typescript
88
+ // Get store details
89
+ const store = await swat.store.info();
90
+ console.log(store.name); // "My Store"
91
+ console.log(store.currency); // "usd"
92
+ console.log(store.colors); // { primary: "#3B82F6", ... }
93
+ \`\`\`
94
+
95
+ ## Types
96
+
97
+ All responses are fully typed. Import types if needed:
98
+
99
+ \`\`\`typescript
100
+ import type { Product, Cart, CheckoutSession, StoreInfo } from '@swatbloc/sdk';
101
+ \`\`\`
102
+
103
+ ## Error Handling
104
+
105
+ \`\`\`typescript
106
+ try {
107
+ const product = await swat.products.get('invalid-id');
108
+ } catch (error) {
109
+ console.error(error.message); // "Product not found: invalid-id"
110
+ }
111
+ \`\`\`
112
+
113
+ ## Important Notes
114
+
115
+ - Always use the PUBLIC key (pk_live_...) in client-side code
116
+ - Never expose your SECRET key (sk_live_...) in frontend code
117
+ - Cart IDs persist across sessions - save them to localStorage
118
+ - Checkout URLs expire after 24 hours
119
+ `;
120
+ export const projectRulesPrompt = {
121
+ name: "project-rules",
122
+ description: "Get the project rules and guidelines for SwatBloc SDK",
123
+ execute: async () => PROJECT_RULES_CONTENT
124
+ };
package/dist/server.js ADDED
@@ -0,0 +1,102 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { ListPromptsRequestSchema, GetPromptRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
4
+ import { prompts } from "./prompts/index.js";
5
+ import { tools } from "./tools/index.js";
6
+ import { zodToJsonSchema } from "zod-to-json-schema";
7
+ export class McpServer {
8
+ server;
9
+ constructor() {
10
+ this.server = new Server({
11
+ name: "megaplatform-mcp",
12
+ version: "0.1.0",
13
+ }, {
14
+ capabilities: {
15
+ prompts: {},
16
+ tools: {},
17
+ },
18
+ });
19
+ this.setupHandlers();
20
+ }
21
+ setupHandlers() {
22
+ // Prompts
23
+ this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
24
+ return {
25
+ prompts: prompts.map((p) => ({
26
+ name: p.name,
27
+ description: p.description,
28
+ })),
29
+ };
30
+ });
31
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
32
+ const prompt = prompts.find((p) => p.name === request.params.name);
33
+ if (!prompt) {
34
+ throw new McpError(ErrorCode.MethodNotFound, "Prompt not found");
35
+ }
36
+ const result = await prompt.execute(request.params.arguments || {});
37
+ return {
38
+ messages: [
39
+ {
40
+ role: "user",
41
+ content: {
42
+ type: "text",
43
+ text: "Generative request", // Placeholder, commonly ignored by prompt users who just want the context
44
+ },
45
+ },
46
+ {
47
+ role: "assistant",
48
+ content: {
49
+ type: "text",
50
+ text: result,
51
+ },
52
+ },
53
+ ],
54
+ };
55
+ });
56
+ // Tools
57
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
58
+ return {
59
+ tools: tools.map((t) => ({
60
+ name: t.name,
61
+ description: t.description,
62
+ inputSchema: zodToJsonSchema(t.schema.input),
63
+ })),
64
+ };
65
+ });
66
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
67
+ const tool = tools.find((t) => t.name === request.params.name);
68
+ if (!tool) {
69
+ throw new McpError(ErrorCode.MethodNotFound, "Tool not found");
70
+ }
71
+ try {
72
+ // Validate arguments
73
+ const args = tool.schema.input.parse(request.params.arguments);
74
+ const result = await tool.execute(args);
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text",
79
+ text: JSON.stringify(result, null, 2),
80
+ },
81
+ ],
82
+ };
83
+ }
84
+ catch (error) {
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `Error: ${error.message}`,
90
+ },
91
+ ],
92
+ isError: true,
93
+ };
94
+ }
95
+ });
96
+ }
97
+ async run() {
98
+ const transport = new StdioServerTransport();
99
+ await this.server.connect(transport);
100
+ console.error("MCP Server running on stdio");
101
+ }
102
+ }
@@ -0,0 +1,383 @@
1
+ import { z } from "zod";
2
+ import { supabase } from "@repo/database";
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 { store_id, name, slug, fields } = args;
43
+ // Check if exists (Idempotency)
44
+ const { data: existing } = await supabase
45
+ .from('content_models')
46
+ .select('*')
47
+ .eq('store_id', store_id)
48
+ .eq('slug', slug)
49
+ .single();
50
+ if (existing) {
51
+ return {
52
+ message: "Database already exists",
53
+ model: existing
54
+ };
55
+ }
56
+ // Rate Limit / Safety Check: Max 50 tables per store
57
+ const { count } = await supabase
58
+ .from('content_models')
59
+ .select('*', { count: 'exact', head: true })
60
+ .eq('store_id', store_id);
61
+ if (count && count >= 50) {
62
+ return {
63
+ message: "Error: Maximum number of databases (50) reached for this store. Please delete some before creating more.",
64
+ error: true
65
+ };
66
+ }
67
+ const { data: newModel, error } = await supabase
68
+ .from('content_models')
69
+ .insert({
70
+ store_id,
71
+ name,
72
+ slug,
73
+ schema: { fields }
74
+ })
75
+ .select()
76
+ .single();
77
+ if (error)
78
+ throw new Error(error.message);
79
+ return {
80
+ message: "Database created successfully",
81
+ model: newModel
82
+ };
83
+ }
84
+ },
85
+ // ==================== PRODUCT TOOLS ====================
86
+ {
87
+ name: "list_products",
88
+ description: "List all products for a store. Returns product titles, prices, and IDs.",
89
+ schema: {
90
+ input: z.object({
91
+ store_id: z.string().describe("The ID of the store to list products for."),
92
+ limit: z.number().optional().describe("Maximum number of products to return (default: 50)"),
93
+ published_only: z.boolean().optional().describe("Only return published products (default: false)")
94
+ })
95
+ },
96
+ execute: async (args) => {
97
+ const { store_id, limit = 50, published_only = false } = args;
98
+ let query = supabase
99
+ .from('products')
100
+ .select('id, title, slug, price, published, options')
101
+ .eq('store_id', store_id)
102
+ .order('created_at', { ascending: false })
103
+ .limit(limit);
104
+ if (published_only) {
105
+ query = query.eq('published', true);
106
+ }
107
+ const { data: products, error } = await query;
108
+ if (error)
109
+ throw new Error(error.message);
110
+ return {
111
+ message: `Found ${products?.length || 0} products`,
112
+ products: products || []
113
+ };
114
+ }
115
+ },
116
+ {
117
+ name: "manage_product",
118
+ 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.",
119
+ schema: {
120
+ input: z.object({
121
+ store_id: z.string().describe("The ID of the store."),
122
+ action: z.enum(['create', 'update', 'delete']).describe("The action to perform."),
123
+ product_id: z.string().optional().describe("Required for update/delete actions."),
124
+ data: z.object({
125
+ title: z.string().optional().describe("Product title"),
126
+ description: z.string().optional().describe("Product description"),
127
+ price: z.number().optional().describe("Product price in cents (e.g., 1999 = $19.99)"),
128
+ compare_at_price: z.number().optional().describe("Compare at price in cents"),
129
+ options: z.array(z.object({
130
+ name: z.string().describe("Option name, e.g., 'Size' or 'Color'"),
131
+ values: z.array(z.string()).describe("Option values, e.g., ['S', 'M', 'L']")
132
+ })).optional().describe("Product options for variant generation"),
133
+ published: z.boolean().optional().describe("Whether the product is published"),
134
+ category: z.string().optional().describe("Product category"),
135
+ sku: z.string().optional().describe("Product SKU"),
136
+ images: z.array(z.string()).optional().describe("Array of image URLs")
137
+ }).optional().describe("Product data for create/update")
138
+ })
139
+ },
140
+ execute: async (args) => {
141
+ const { store_id, action, product_id, data } = args;
142
+ if (action === 'create') {
143
+ if (!data?.title) {
144
+ return { error: true, message: "Title is required to create a product" };
145
+ }
146
+ const slug = data.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') + '-' + Math.random().toString(36).substring(2, 7);
147
+ const productData = {
148
+ store_id,
149
+ title: data.title,
150
+ slug,
151
+ description: data.description || '',
152
+ price: data.price || 0,
153
+ compare_at_price: data.compare_at_price || null,
154
+ options: data.options || [],
155
+ published: data.published ?? false,
156
+ category: data.category || null,
157
+ sku: data.sku || null,
158
+ images: data.images || []
159
+ };
160
+ const { data: product, error } = await supabase
161
+ .from('products')
162
+ .insert(productData)
163
+ .select()
164
+ .single();
165
+ if (error)
166
+ throw new Error(error.message);
167
+ return {
168
+ message: "Product created successfully",
169
+ product
170
+ };
171
+ }
172
+ if (action === 'update') {
173
+ if (!product_id) {
174
+ return { error: true, message: "product_id is required for update" };
175
+ }
176
+ const updateData = {};
177
+ if (data?.title)
178
+ updateData.title = data.title;
179
+ if (data?.description !== undefined)
180
+ updateData.description = data.description;
181
+ if (data?.price !== undefined)
182
+ updateData.price = data.price;
183
+ if (data?.compare_at_price !== undefined)
184
+ updateData.compare_at_price = data.compare_at_price;
185
+ if (data?.options)
186
+ updateData.options = data.options;
187
+ if (data?.published !== undefined)
188
+ updateData.published = data.published;
189
+ if (data?.category !== undefined)
190
+ updateData.category = data.category;
191
+ if (data?.sku !== undefined)
192
+ updateData.sku = data.sku;
193
+ if (data?.images)
194
+ updateData.images = data.images;
195
+ const { data: product, error } = await supabase
196
+ .from('products')
197
+ .update(updateData)
198
+ .eq('id', product_id)
199
+ .eq('store_id', store_id)
200
+ .select()
201
+ .single();
202
+ if (error)
203
+ throw new Error(error.message);
204
+ return {
205
+ message: "Product updated successfully",
206
+ product
207
+ };
208
+ }
209
+ if (action === 'delete') {
210
+ if (!product_id) {
211
+ return { error: true, message: "product_id is required for delete" };
212
+ }
213
+ const { error } = await supabase
214
+ .from('products')
215
+ .delete()
216
+ .eq('id', product_id)
217
+ .eq('store_id', store_id);
218
+ if (error)
219
+ throw new Error(error.message);
220
+ return {
221
+ message: "Product deleted successfully"
222
+ };
223
+ }
224
+ return { error: true, message: "Invalid action" };
225
+ }
226
+ },
227
+ {
228
+ name: "manage_variant",
229
+ description: "Create, update, delete, or auto-generate variants for a product. Use 'generate' action to automatically create variants from product options (Cartesian product).",
230
+ schema: {
231
+ input: z.object({
232
+ product_id: z.string().describe("The ID of the product."),
233
+ action: z.enum(['create', 'update', 'delete', 'generate', 'list']).describe("The action to perform."),
234
+ variant_id: z.string().optional().describe("Required for update/delete actions."),
235
+ data: z.object({
236
+ title: z.string().optional().describe("Variant title (e.g., 'S / Red')"),
237
+ sku: z.string().optional().describe("Variant SKU"),
238
+ price: z.number().optional().describe("Variant price in cents"),
239
+ inventory_quantity: z.number().optional().describe("Inventory count"),
240
+ options: z.record(z.string(), z.string()).optional().describe("Option values, e.g., { 'Size': 'S', 'Color': 'Red' }")
241
+ }).optional().describe("Variant data for create/update"),
242
+ replace_existing: z.boolean().optional().describe("For 'generate': replace existing variants (default: true)")
243
+ })
244
+ },
245
+ execute: async (args) => {
246
+ const { product_id, action, variant_id, data, replace_existing = true } = args;
247
+ // Verify product exists
248
+ const { data: product, error: productError } = await supabase
249
+ .from('products')
250
+ .select('id, price, options')
251
+ .eq('id', product_id)
252
+ .single();
253
+ if (productError || !product) {
254
+ return { error: true, message: "Product not found" };
255
+ }
256
+ if (action === 'list') {
257
+ const { data: variants, error } = await supabase
258
+ .from('product_variants')
259
+ .select('id, title, sku, price, inventory_quantity, options')
260
+ .eq('product_id', product_id)
261
+ .order('title');
262
+ if (error)
263
+ throw new Error(error.message);
264
+ return {
265
+ message: `Found ${variants?.length || 0} variants`,
266
+ variants: variants || []
267
+ };
268
+ }
269
+ if (action === 'generate') {
270
+ const options = product.options || [];
271
+ if (!options.length || !options.every((o) => o.values?.length > 0)) {
272
+ return {
273
+ error: true,
274
+ message: "Product must have at least one option with values defined. Update the product options first."
275
+ };
276
+ }
277
+ const combinations = generateCartesianProduct(options);
278
+ if (combinations.length > 100) {
279
+ return {
280
+ error: true,
281
+ message: `Too many variants (${combinations.length}). Maximum is 100. Reduce option values.`
282
+ };
283
+ }
284
+ if (replace_existing) {
285
+ await supabase
286
+ .from('product_variants')
287
+ .delete()
288
+ .eq('product_id', product_id);
289
+ }
290
+ const variantsToInsert = combinations.map(combo => ({
291
+ product_id,
292
+ title: Object.values(combo).join(' / '),
293
+ sku: null,
294
+ price: product.price,
295
+ inventory_quantity: 0,
296
+ options: combo,
297
+ description: null,
298
+ image_url: null,
299
+ images: []
300
+ }));
301
+ const { data: variants, error } = await supabase
302
+ .from('product_variants')
303
+ .insert(variantsToInsert)
304
+ .select();
305
+ if (error)
306
+ throw new Error(error.message);
307
+ return {
308
+ message: `Generated ${variants?.length || 0} variants from options`,
309
+ variants: variants || []
310
+ };
311
+ }
312
+ if (action === 'create') {
313
+ const variantData = {
314
+ product_id,
315
+ title: data?.title || 'New Variant',
316
+ sku: data?.sku || null,
317
+ price: data?.price ?? product.price,
318
+ inventory_quantity: data?.inventory_quantity || 0,
319
+ options: data?.options || {},
320
+ description: null,
321
+ image_url: null,
322
+ images: []
323
+ };
324
+ const { data: variant, error } = await supabase
325
+ .from('product_variants')
326
+ .insert(variantData)
327
+ .select()
328
+ .single();
329
+ if (error)
330
+ throw new Error(error.message);
331
+ return {
332
+ message: "Variant created successfully",
333
+ variant
334
+ };
335
+ }
336
+ if (action === 'update') {
337
+ if (!variant_id) {
338
+ return { error: true, message: "variant_id is required for update" };
339
+ }
340
+ const updateData = {};
341
+ if (data?.title)
342
+ updateData.title = data.title;
343
+ if (data?.sku !== undefined)
344
+ updateData.sku = data.sku;
345
+ if (data?.price !== undefined)
346
+ updateData.price = data.price;
347
+ if (data?.inventory_quantity !== undefined)
348
+ updateData.inventory_quantity = data.inventory_quantity;
349
+ if (data?.options)
350
+ updateData.options = data.options;
351
+ const { data: variant, error } = await supabase
352
+ .from('product_variants')
353
+ .update(updateData)
354
+ .eq('id', variant_id)
355
+ .eq('product_id', product_id)
356
+ .select()
357
+ .single();
358
+ if (error)
359
+ throw new Error(error.message);
360
+ return {
361
+ message: "Variant updated successfully",
362
+ variant
363
+ };
364
+ }
365
+ if (action === 'delete') {
366
+ if (!variant_id) {
367
+ return { error: true, message: "variant_id is required for delete" };
368
+ }
369
+ const { error } = await supabase
370
+ .from('product_variants')
371
+ .delete()
372
+ .eq('id', variant_id)
373
+ .eq('product_id', product_id);
374
+ if (error)
375
+ throw new Error(error.message);
376
+ return {
377
+ message: "Variant deleted successfully"
378
+ };
379
+ }
380
+ return { error: true, message: "Invalid action" };
381
+ }
382
+ }
383
+ ];
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@swatbloc/mcp-server",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "private": false,
8
+ "main": "dist/index.js",
9
+ "bin": {
10
+ "swatbloc-mcp": "dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "start": "node dist/index.js",
19
+ "dev": "tsc --watch",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^0.6.0",
24
+ "zod": "^4.3.5",
25
+ "zod-to-json-schema": "^3.25.1"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.10.6",
29
+ "typescript": "^5.3.3"
30
+ }
31
+ }