@storelayer/mcp-server 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/SKILL.md +405 -199
  2. package/dist/index.js +32 -10
  3. package/package.json +25 -25
package/SKILL.md CHANGED
@@ -1,276 +1,482 @@
1
- # Store Layer MCP Server Skill Guide for Claude Desktop
1
+ # Store Layer AI Skill Guide
2
2
 
3
- You have access to the **Store Layer** MCP server, which lets you manage loyalty programs, promotions, wallets, users, events, referrals, and more for a specific project.
3
+ You have access to the **Store Layer** MCP server. This guide helps you build loyalty programs, promotions, and customer engagement systems effectively.
4
4
 
5
- ## How It Works
5
+ ## Pipeline: How to Build Loyalty Programs
6
6
 
7
- All tools are dynamically loaded from the Store Layer API. Tool names use underscores (e.g., `promotions_evaluate_cart`). Tools that require a `userId` parameter operate on a specific external user's data.
7
+ **Always follow this pipeline** when building loyalty features. Do not skip steps.
8
8
 
9
- ---
9
+ ### Step 1: Discover Current State
10
+
11
+ Before creating anything, understand what exists:
12
+
13
+ ```
14
+ 1. project_get_config → currency, timezone, cart format
15
+ 2. project_list_rules → existing loyalty rules
16
+ 3. promotions_get_active → active promotions
17
+ 4. events_get_stats → event flow (what types come in)
18
+ 5. wallet_get_balance (userId) → existing asset types in use
19
+ ```
20
+
21
+ ### Step 2: Design the Program
22
+
23
+ Map the user's intent to domains:
24
+
25
+ | User wants... | Domains involved |
26
+ | ---------------------------- | ------------------------------- |
27
+ | "Earn points on purchase" | Rules + Wallet |
28
+ | "10% off orders over $50" | Promotions |
29
+ | "Buy 2 get 1 free" | Promotions (script) |
30
+ | "Refer a friend, get $10" | Referral + Wallet |
31
+ | "Double points this weekend" | Rules (with date conditions) |
32
+ | "Coupon code for 20% off" | Promotions + Coupons |
33
+ | "VIP tier discounts" | Promotions (conditions on user) |
34
+
35
+ **Propose the full plan before creating anything.** Show the user what you'll create.
10
36
 
11
- ## Available Tools by Domain
12
-
13
- ### External Users (`external_users`)
14
- | Tool | Description |
15
- |------|-------------|
16
- | `external_users_get_user` | Get a user by ID |
17
- | `external_users_list_users` | List users with pagination |
18
- | `external_users_lookup_user` | Smart lookup: tries ID match, then email fallback |
19
- | `external_users_register` | Register a new user |
20
- | `external_users_update` | Update a user |
21
- | `external_users_remove` | Remove a user |
22
-
23
- ### Wallet (`wallet`) all require `userId`
24
- | Tool | Description |
25
- |------|-------------|
26
- | `wallet_get_balance` | Get balance for all asset types |
27
- | `wallet_list_transactions` | List transaction history with filters |
28
- | `wallet_credit` | Add assets (points, tokens, etc.) |
29
- | `wallet_debit` | Remove assets |
30
-
31
- ### Loyalty Events (`events`)
32
- | Tool | Description |
33
- |------|-------------|
34
- | `events_get` | Get a single event by ID |
35
- | `events_list` | List events with filters (type, status, pagination) |
36
- | `events_get_stats` | Aggregate stats: total, processed, pending |
37
-
38
- ### Referral (`referral`)
39
- | Tool | Description |
40
- |------|-------------|
41
- | `referral_get_config` | Get referral program config |
42
- | `referral_list_codes` | List referral codes with filters |
43
- | `referral_get_code_by_referrer` | Look up code by referrer ID |
44
- | `referral_validate_code` | Check if a code is valid |
45
- | `referral_list_referrals` | List referral records |
46
- | `referral_list_events` | List referral audit events |
47
- | `referral_get_stats` | Aggregate stats |
48
- | `referral_get_leaderboard` | Leaderboard ranked by referrals |
49
- | `referral_create_code` | Create a referral code |
50
- | `referral_apply_code` | Apply a code for a referee |
51
- | `referral_deactivate_code` | Deactivate a code |
52
-
53
- ### Project (`project`)
54
- | Tool | Description |
55
- |------|-------------|
56
- | `project_get_config` | Get project config (currency, timezone, settings) |
57
- | `project_list_rules` | List loyalty rules |
58
- | `project_get_rule` | Get a single rule by ID |
59
- | `project_list_integrations` | List integrations |
60
- | `project_list_resources` | List resource definitions |
61
- | `project_add_rule` | Create a loyalty rule |
62
- | `project_update_rule` | Update a rule |
63
- | `project_remove_rule` | Delete a rule |
64
- | `project_test_conditions` | Test conditions against sample data |
65
-
66
- ### Promotions (`promotions`)
67
- | Tool | Description |
68
- |------|-------------|
69
- | `promotions_list` | List promotions with filters |
70
- | `promotions_get_active` | Get all active promotions |
71
- | `promotions_list_coupons` | List coupons |
72
- | `promotions_list_usage` | List usage/redemption records |
73
- | `promotions_get_stats` | Stats for a specific promotion |
74
- | `promotions_get_aggregate_stats` | Aggregate stats across all promotions |
75
- | `promotions_create` | Create a promotion |
76
- | `promotions_create_coupon` | Create a coupon for a promotion |
77
- | `promotions_record_usage` | Record a redemption |
78
- | `promotions_evaluate_cart` | **Evaluate a cart against active promotions** |
79
-
80
- ### Support (`support`)
81
- | Tool | Description |
82
- |------|-------------|
83
- | `support_get_ticket` | Get a ticket by ID |
84
- | `support_list_tickets` | List tickets with filters |
85
- | `support_get_stats` | Aggregate ticket stats |
86
- | `support_create_ticket` | Create a ticket |
87
- | `support_update_ticket` | Update a ticket |
88
-
89
- ### Surveys (`surveys`)
90
- | Tool | Description |
91
- |------|-------------|
92
- | `surveys_get` | Get a survey by ID |
93
- | `surveys_list` | List surveys |
94
- | `surveys_list_responses` | List responses for a survey |
95
- | `surveys_get_stats` | Survey analytics |
96
- | `surveys_create` | Create a survey |
97
- | `surveys_submit_response` | Submit a response |
98
-
99
- ### Workflows (`workflows`)
100
- | Tool | Description |
101
- |------|-------------|
102
- | `workflows_list` | List workflow executions |
103
- | `workflows_get` | Get a workflow execution with steps |
104
-
105
- ### Stores (`stores`)
106
- | Tool | Description |
107
- |------|-------------|
108
- | `stores_get_store` / `stores_list_stores` | Read stores |
109
- | `stores_create_store` / `stores_update_store` / `stores_remove_store` | Manage stores |
110
- | `stores_get_facility` / `stores_list_facilities` | Read facilities |
111
- | `stores_create_facility` / `stores_update_facility` / `stores_remove_facility` | Manage facilities |
112
-
113
- ### Resources (`resources`)
114
- | Tool | Description |
115
- |------|-------------|
116
- | `resources_list` / `resources_get` | Read resource definitions |
117
- | `resources_add` / `resources_update` / `resources_remove` | Manage resource definitions |
37
+ ### Step 3: Build & Test Incrementally
38
+
39
+ For **rules**: draft conditions → test with `project_test_conditions` → create rule
40
+ For **promotions**: create as `draft` → test with `promotions_evaluate_cart` → activate
41
+ For **coupons**: create coupon → test cart with coupon code → verify
42
+
43
+ ### Step 4: Verify End-to-End
44
+
45
+ After building, verify the chain works:
46
+
47
+ - For rules: event type conditions → actions → wallet credit
48
+ - For promotions: cart → conditions → script → discounts
49
+ - Summarize what was built and how pieces connect
118
50
 
119
51
  ---
120
52
 
121
- ## Domain Knowledge
53
+ ## Recipes
122
54
 
123
- ### Promotions Engine
55
+ ### Recipe 1: Points-for-Purchase (Earn 1 point per dollar)
124
56
 
125
- Every promotion has an `itemsDiscountComputation` script that computes per-item discounts. There are no predefined discount types — the script IS the discount logic.
57
+ **Domains:** Rules + Wallet
126
58
 
127
- **Script environment:**
128
- - `$('cart')` — full cart object. `$('cart').items` is the items array. Each item: `{ id, price, quantity, category, tags, ...custom }`
129
- - `$('cart').total` — cart total
130
- - `$('user')` — current user (if userId provided)
131
- - `$('couponCodes')` — applied coupon codes
59
+ **Step 1:** Create the rule:
132
60
 
133
- **Return format:** Array of `{ id: string, amount: number }` where `id` = item ID and `amount` = discount to subtract.
61
+ ```json
62
+ project_add_rule({
63
+ "name": "1 Point Per Dollar",
64
+ "conditions": {
65
+ "conditions": [
66
+ { "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "purchase", "rightType": "string" }
67
+ ],
68
+ "combinator": "AND"
69
+ },
70
+ "actions": [
71
+ {
72
+ "type": "reward",
73
+ "config": {
74
+ "assetType": "points",
75
+ "amount": "{{ event.payload.amount }}",
76
+ "description": "Purchase reward: {{ event.payload.amount }} points"
77
+ }
78
+ }
79
+ ],
80
+ "resources": {
81
+ "event": { "type": "purchase" }
82
+ }
83
+ })
84
+ ```
85
+
86
+ **Step 2:** Test conditions:
87
+
88
+ ```json
89
+ project_test_conditions({
90
+ "conditions": {
91
+ "conditions": [
92
+ { "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "purchase", "rightType": "string" }
93
+ ],
94
+ "combinator": "AND"
95
+ },
96
+ "context": {
97
+ "event": { "type": "purchase", "payload": { "amount": 49.99 } }
98
+ }
99
+ })
100
+ ```
134
101
 
135
- **Condition system:**
136
- - Structure: `{ conditions: [{ leftValue, operator, rightValue, rightType }], combinator: "AND" | "OR" }`
137
- - Fields: `cart.total`, `cart.itemCount`, `cart.uniqueItemCount`, `cart.items[0].price`, etc.
138
- - Operators: `equals`, `notEquals`, `gt`, `gte`, `lt`, `lte`, `contains`, `notContains`, `startsWith`, `endsWith`, `exists`, `notExists`, `regex`, `before`, `after`, `isEmpty`, `isNotEmpty`, `hasKey`
102
+ **Step 3:** Verify wallet (for a test user):
139
103
 
140
- **Stacking modes:**
141
- - `stackable` combines with other promotions (default)
142
- - `exclusive` — highest priority wins, all others excluded
143
- - `exclusive_group` — highest priority within its group wins
104
+ ```json
105
+ wallet_get_balance({ "userId": "test-user-123" })
106
+ ```
107
+
108
+ ---
144
109
 
145
- **Cart formats:**
146
- - **Standard:** Items have `quantity` field. Engine expands internally.
147
- ```json
148
- { "items": [{ "id": "muffin-001", "price": 3.50, "quantity": 2 }] }
149
- ```
150
- - **Sub-items (pre-expanded):** Each unit is a separate entry with a unique ID and a shared logical ID in another field. Pass `itemIdField` to specify which field to use.
151
- ```json
152
- { "items": [{ "id": "sub-1", "sku": "muffin-001", "price": 3.50 }, { "id": "sub-2", "sku": "muffin-001", "price": 3.50 }] }
153
- ```
110
+ ### Recipe 2: Percentage Discount (10% off everything)
154
111
 
155
- **Coupon system:** Promotions can require a coupon code (`requiresCoupon: true`). Create coupons with `promotions_create_coupon`, validate them during cart evaluation by passing `couponCodes`.
112
+ **Domains:** Promotions
156
113
 
157
- #### Promotion Examples
114
+ **Step 1:** Create promotion in draft:
158
115
 
159
- **10% off all items:**
160
116
  ```json
161
- {
117
+ promotions_create({
162
118
  "name": "10% Off Everything",
163
- "status": "active",
119
+ "status": "draft",
164
120
  "conditions": { "conditions": [], "combinator": "AND" },
165
121
  "itemsDiscountComputation": {
166
122
  "script": "const items = $('cart').items;\nreturn items.map(item => ({ id: item.id, amount: item.price * 0.10 }));",
167
123
  "language": "javascript"
168
124
  }
169
- }
125
+ })
170
126
  ```
171
127
 
172
- **$5 off orders over $50:**
128
+ **Step 2:** Test with a cart:
129
+
173
130
  ```json
174
- {
175
- "name": "$5 Off Over $50",
176
- "status": "active",
131
+ promotions_evaluate_cart({
132
+ "cart": {
133
+ "items": [
134
+ { "id": "item-1", "price": 25.00, "quantity": 1 },
135
+ { "id": "item-2", "price": 15.00, "quantity": 2 }
136
+ ]
137
+ }
138
+ })
139
+ ```
140
+
141
+ **Step 3:** Check results — verify `summary.totalDiscount` is 10% of total. If correct, activate:
142
+
143
+ ```json
144
+ promotions_update({ "promotionId": "promo_xxx", "status": "active" })
145
+ ```
146
+
147
+ ---
148
+
149
+ ### Recipe 3: Threshold Discount ($5 off orders over $50)
150
+
151
+ **Domains:** Promotions
152
+
153
+ ```json
154
+ promotions_create({
155
+ "name": "$5 Off Orders Over $50",
156
+ "status": "draft",
177
157
  "conditions": {
178
- "conditions": [{ "leftValue": "cart.total", "operator": "gte", "rightValue": 50, "rightType": "number" }],
158
+ "conditions": [
159
+ { "leftValue": "cart.total", "operator": "gte", "rightValue": 50, "rightType": "number" }
160
+ ],
179
161
  "combinator": "AND"
180
162
  },
181
163
  "itemsDiscountComputation": {
182
164
  "script": "const items = $('cart').items;\nconst total = items.reduce((s, i) => s + i.price, 0);\nreturn items.map(item => ({ id: item.id, amount: (item.price / total) * 5 }));",
183
165
  "language": "javascript"
184
166
  }
185
- }
167
+ })
186
168
  ```
187
169
 
188
- **BOGO: buy 2+ shoes, cheapest free:**
170
+ **Key:** The script distributes the $5 proportionally across items by their price share.
171
+
172
+ ---
173
+
174
+ ### Recipe 4: BOGO (Buy 2+ shoes, cheapest free)
175
+
176
+ **Domains:** Promotions
177
+
189
178
  ```json
190
- {
179
+ promotions_create({
180
+ "name": "BOGO Shoes",
181
+ "status": "draft",
182
+ "conditions": { "conditions": [], "combinator": "AND" },
191
183
  "itemsDiscountComputation": {
192
184
  "script": "const shoes = $('cart').items.filter(i => i.category === 'shoes');\nif (shoes.length < 2) return [];\nconst sorted = [...shoes].sort((a, b) => a.price - b.price);\nreturn [{ id: sorted[0].id, amount: sorted[0].price }];",
193
185
  "language": "javascript"
194
186
  }
195
- }
187
+ })
196
188
  ```
197
189
 
198
- #### Cart Evaluation
190
+ **Test cart must include `category` field on items**, or the filter returns nothing.
191
+
192
+ ---
193
+
194
+ ### Recipe 5: Coupon Code (20% off with code SAVE20)
195
+
196
+ **Domains:** Promotions + Coupons
197
+
198
+ **Step 1:** Create promotion requiring coupon:
199
199
 
200
- Use `promotions_evaluate_cart` with:
201
200
  ```json
202
- {
203
- "cart": {
204
- "items": [
205
- { "id": "item-1", "price": 25.00, "quantity": 2, "category": "shoes" },
206
- { "id": "item-2", "price": 15.00, "quantity": 1, "category": "accessories" }
207
- ]
208
- },
209
- "userId": "user_123",
210
- "couponCodes": ["SAVE10"]
211
- }
201
+ promotions_create({
202
+ "name": "20% Off with Code",
203
+ "status": "active",
204
+ "requiresCoupon": true,
205
+ "conditions": { "conditions": [], "combinator": "AND" },
206
+ "itemsDiscountComputation": {
207
+ "script": "const items = $('cart').items;\nreturn items.map(item => ({ id: item.id, amount: item.price * 0.20 }));",
208
+ "language": "javascript"
209
+ }
210
+ })
211
+ ```
212
+
213
+ **Step 2:** Create the coupon:
214
+
215
+ ```json
216
+ promotions_create_coupon({
217
+ "promotionId": "promo_xxx",
218
+ "code": "SAVE20",
219
+ "maxUses": 1000
220
+ })
212
221
  ```
213
222
 
214
- Response includes: `summary` (originalTotal, totalDiscount, finalTotal), `items` (per-item discounts), `appliedPromotions`, `evaluatedPromotions` (with condition/computation match details), and `warnings`.
223
+ **Step 3:** Test with coupon:
224
+
225
+ ```json
226
+ promotions_evaluate_cart({
227
+ "cart": { "items": [{ "id": "item-1", "price": 50.00, "quantity": 1 }] },
228
+ "couponCodes": ["SAVE20"]
229
+ })
230
+ ```
215
231
 
216
232
  ---
217
233
 
218
- ### Loyalty Rules
234
+ ### Recipe 6: Referral Program (Refer a friend, both get 500 points)
235
+
236
+ **Domains:** Referral + Rules + Wallet
237
+
238
+ **Step 1:** Check referral config:
239
+
240
+ ```json
241
+ referral_get_config()
242
+ ```
219
243
 
220
- Rules trigger when events are ingested. Each rule has conditions (evaluated against runtime context) and actions (executed when conditions match).
244
+ **Step 2:** Create referral code for a user:
221
245
 
222
- **Condition expressions** use Handlebars syntax: `{{ event.type }}`, `{{ event.payload.amount }}`, `{{ user.email }}`, `{{ history.amount }}`
246
+ ```json
247
+ referral_create_code({ "referrerId": "user-123" })
248
+ ```
223
249
 
224
- **Action types:**
225
- - `reward` — `{ assetType: "points", amount: 10, description: "Purchase reward" }`. Dynamic: `amount: "{{ event.amount }}"`
226
- - `integration` — `{ integrationId: "intg_xxx" }` — triggers webhook/Telegram/Slack
227
- - `apply_referral` / `complete_referral` / `mark_code_used` — referral lifecycle actions
250
+ **Step 3:** Set up a rule to reward on completed referral:
228
251
 
229
- **Rule example — 1 point per dollar spent:**
230
252
  ```json
231
- {
232
- "name": "Dollar-to-Points",
233
- "eventType": "purchase",
253
+ project_add_rule({
254
+ "name": "Referral Reward",
234
255
  "conditions": {
235
- "conditions": [{ "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "purchase", "rightType": "string" }],
256
+ "conditions": [
257
+ { "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "referral.completed", "rightType": "string" }
258
+ ],
236
259
  "combinator": "AND"
237
260
  },
238
- "actions": [{ "type": "reward", "config": { "assetType": "points", "amount": "{{ event.payload.amount }}", "description": "1 point per dollar" } }]
239
- }
261
+ "actions": [
262
+ {
263
+ "type": "reward",
264
+ "config": {
265
+ "assetType": "points",
266
+ "amount": 500,
267
+ "description": "Referral reward"
268
+ }
269
+ }
270
+ ],
271
+ "resources": {
272
+ "event": { "type": "referral.completed" }
273
+ }
274
+ })
240
275
  ```
241
276
 
242
277
  ---
243
278
 
244
- ### Events & Ingestion
279
+ ### Recipe 7: Stacking Promotions (Member discount + seasonal sale)
245
280
 
246
- Events are sent externally via `POST /api/ingest/events` with an API key. The MCP server can read events but not ingest them directly.
281
+ **Domains:** Promotions (stacking)
247
282
 
248
- **Processing flow:** Event stored → queued → rules matched by `eventType` → resources resolved → conditions evaluated → actions executed → event marked processed.
283
+ Create two stackable promotions:
249
284
 
250
- **Event payload example:**
251
285
  ```json
252
- { "type": "purchase", "userId": "user123", "payload": { "orderId": "order_abc", "amount": 99.99, "items": [...] } }
286
+ // Always-on member discount (lower priority)
287
+ promotions_create({
288
+ "name": "Member 5% Discount",
289
+ "status": "active",
290
+ "stackingMode": "stackable",
291
+ "priority": 1,
292
+ "conditions": { "conditions": [], "combinator": "AND" },
293
+ "itemsDiscountComputation": {
294
+ "script": "return $('cart').items.map(i => ({ id: i.id, amount: i.price * 0.05 }));",
295
+ "language": "javascript"
296
+ }
297
+ })
298
+
299
+ // Seasonal sale (higher priority, also stackable)
300
+ promotions_create({
301
+ "name": "Summer Sale 15%",
302
+ "status": "active",
303
+ "stackingMode": "stackable",
304
+ "priority": 10,
305
+ "validFrom": "2026-06-01T00:00:00Z",
306
+ "validTo": "2026-08-31T23:59:59Z",
307
+ "conditions": { "conditions": [], "combinator": "AND" },
308
+ "itemsDiscountComputation": {
309
+ "script": "return $('cart').items.map(i => ({ id: i.id, amount: i.price * 0.15 }));",
310
+ "language": "javascript"
311
+ }
312
+ })
253
313
  ```
254
314
 
315
+ Both apply. Use `exclusive` stacking mode if only the best should win.
316
+
317
+ ---
318
+
319
+ ## Tool Reference
320
+
321
+ ### Promotions (`promotions_*`)
322
+
323
+ | Tool | Type | Description |
324
+ | -------------------------------- | ----- | -------------------------------- |
325
+ | `promotions_list` | read | List promotions with filters |
326
+ | `promotions_get_active` | read | Get all active promotions |
327
+ | `promotions_list_coupons` | read | List coupons for a promotion |
328
+ | `promotions_list_usage` | read | List usage/redemption records |
329
+ | `promotions_get_stats` | read | Stats for a specific promotion |
330
+ | `promotions_get_aggregate_stats` | read | Global promotion stats |
331
+ | `promotions_create` | write | Create a promotion |
332
+ | `promotions_update` | write | Update a promotion |
333
+ | `promotions_remove` | write | Delete a promotion |
334
+ | `promotions_duplicate` | write | Clone a promotion as draft |
335
+ | `promotions_create_coupon` | write | Create a coupon |
336
+ | `promotions_bulk_create_coupons` | write | Bulk create 1-1000 coupons |
337
+ | `promotions_evaluate_cart` | write | Evaluate cart against promotions |
338
+
339
+ ### Wallet (`wallet_*`) — all require `userId`
340
+
341
+ | Tool | Type | Description |
342
+ | -------------------------- | ----- | ------------------------------- |
343
+ | `wallet_get_balance` | read | Get balance for all asset types |
344
+ | `wallet_list_transactions` | read | Transaction history |
345
+ | `wallet_credit` | write | Add assets (points, tokens) |
346
+ | `wallet_debit` | write | Spend assets (FEFO order) |
347
+
348
+ ### Rules (`project_*`)
349
+
350
+ | Tool | Type | Description |
351
+ | --------------------------- | ----- | ----------------------------------- |
352
+ | `project_get_config` | read | Project configuration |
353
+ | `project_list_rules` | read | List loyalty rules |
354
+ | `project_get_rule` | read | Get a single rule |
355
+ | `project_list_integrations` | read | List integrations |
356
+ | `project_list_resources` | read | List resource definitions |
357
+ | `project_add_rule` | write | Create a rule |
358
+ | `project_update_rule` | write | Update a rule |
359
+ | `project_remove_rule` | write | Delete a rule |
360
+ | `project_test_conditions` | write | Test conditions against sample data |
361
+
362
+ ### Events (`events_*`)
363
+
364
+ | Tool | Type | Description |
365
+ | ------------------ | ---- | ------------------------ |
366
+ | `events_get` | read | Get event by ID |
367
+ | `events_list` | read | List events with filters |
368
+ | `events_get_stats` | read | Event stats |
369
+
370
+ ### Referral (`referral_*`)
371
+
372
+ | Tool | Type | Description |
373
+ | -------------------------- | ----- | ----------------------- |
374
+ | `referral_get_config` | read | Referral program config |
375
+ | `referral_list_codes` | read | List referral codes |
376
+ | `referral_validate_code` | read | Check if code is valid |
377
+ | `referral_get_stats` | read | Aggregate stats |
378
+ | `referral_get_leaderboard` | read | Top referrers |
379
+ | `referral_create_code` | write | Create a referral code |
380
+ | `referral_apply_code` | write | Apply code for referee |
381
+ | `referral_deactivate_code` | write | Deactivate a code |
382
+
383
+ ### External Users (`external_users_*`)
384
+
385
+ | Tool | Type | Description |
386
+ | ---------------------------- | ----- | ---------------------------- |
387
+ | `external_users_get_user` | read | Get user by ID |
388
+ | `external_users_list_users` | read | List users |
389
+ | `external_users_lookup_user` | read | Smart lookup (ID then email) |
390
+ | `external_users_register` | write | Register a user |
391
+ | `external_users_update` | write | Update a user |
392
+
393
+ ### Skill & Feedback (`skill_*`)
394
+
395
+ | Tool | Type | Description |
396
+ | -------------------------- | ----- | ---------------------------- |
397
+ | `skill_get_content` | read | Get this skill guide content |
398
+ | `skill_list_feedback` | read | List feedback entries |
399
+ | `skill_get_feedback_stats` | read | Feedback analytics |
400
+ | `skill_update_content` | write | Update skill guide |
401
+ | `skill_submit_feedback` | write | Report tool usage outcome |
402
+
403
+ ---
404
+
405
+ ## Discount Script Reference
406
+
407
+ **Available context:**
408
+
409
+ - `$('cart')` — full cart object. `$('cart').items` = items array
410
+ - `$('cart').items[n]` — `{ id, price, quantity, category, tags, ...custom }`
411
+ - `$('cart').total` — cart total
412
+ - `$('user')` — current user (if userId provided)
413
+ - `$('couponCodes')` — applied coupon codes array
414
+
415
+ **Return format:** `Array<{ id: string, amount: number }>` where `id` = item ID, `amount` = discount
416
+
417
+ **Common patterns:**
418
+
419
+ ```javascript
420
+ // Percentage off all items
421
+ return $("cart").items.map((i) => ({ id: i.id, amount: i.price * RATE }));
422
+
423
+ // Fixed amount distributed proportionally
424
+ const total = $("cart").items.reduce((s, i) => s + i.price, 0);
425
+ return $("cart").items.map((i) => ({
426
+ id: i.id,
427
+ amount: (i.price / total) * FIXED_AMOUNT,
428
+ }));
429
+
430
+ // Category-specific discount
431
+ return $("cart")
432
+ .items.filter((i) => i.category === "TARGET")
433
+ .map((i) => ({ id: i.id, amount: i.price * RATE }));
434
+
435
+ // Cheapest item free
436
+ const sorted = [...$("cart").items].sort((a, b) => a.price - b.price);
437
+ return [{ id: sorted[0].id, amount: sorted[0].price }];
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Condition Operators
443
+
444
+ `equals`, `notEquals`, `gt`, `gte`, `lt`, `lte`, `contains`, `notContains`, `startsWith`, `endsWith`, `exists`, `notExists`, `regex`, `before`, `after`, `isEmpty`, `isNotEmpty`, `hasKey`
445
+
446
+ **Condition fields for promotions:** `cart.total`, `cart.itemCount`, `cart.uniqueItemCount`, `cart.items[0].price`, `cart.items[0].category`
447
+
448
+ **Condition expressions for rules:** `{{ event.type }}`, `{{ event.payload.amount }}`, `{{ user.email }}`, `{{ history.amount }}`
449
+
255
450
  ---
256
451
 
257
- ### Resources
452
+ ## Common Mistakes
258
453
 
259
- Resources provide runtime context for rule evaluation. Types:
260
- - **event** — incoming event data
261
- - **http** — external REST API call
262
- - **database** — PostgreSQL query via integration
263
- - **internal** — Engine-X entity data (users, wallets, transactions via registry tools)
264
- - **payload** — request body data
454
+ 1. **Script returns empty array** Usually a field name mismatch. Check the actual cart item fields (e.g., `category` vs `type`). Always test with `promotions_evaluate_cart` first.
265
455
 
266
- Resources are resolved before rule evaluation. Their data is accessible in condition expressions.
456
+ 2. **Promotion not applying** Check `notApplied` array in evaluate response. It tells you exactly which conditions failed and why.
457
+
458
+ 3. **Rule not firing** — Ensure the event type in conditions matches what's being ingested. Use `events_list` to check actual event types.
459
+
460
+ 4. **Forgot to activate** — Promotions created in `draft` won't apply. Update status to `active` after testing.
461
+
462
+ 5. **Coupon not working** — Promotion must have `requiresCoupon: true` AND the coupon must be passed in `couponCodes` array during evaluation.
463
+
464
+ 6. **Stacking conflicts** — `exclusive` promotions block everything else. Use `stackable` for promotions that should combine. Use `exclusive_group` for "best of group" behavior.
465
+
466
+ 7. **Missing event resource in rules** — Rules require `resources: { event: { type: "..." } }`. Without it, the rule won't match any events.
267
467
 
268
468
  ---
269
469
 
270
- ## Best Practices
470
+ ## Feedback
471
+
472
+ After building a loyalty program, use `skill_submit_feedback` to report the outcome. This helps improve this guide over time.
271
473
 
272
- 1. **Start by exploring** — use `promotions_get_aggregate_stats`, `project_get_config`, or list tools to understand the current state before making changes.
273
- 2. **Test before activating** — create promotions in `draft` status, use `promotions_evaluate_cart` to test with sample carts, then set to `active`.
274
- 3. **Use `project_test_conditions`** to validate rule conditions against sample data before saving.
275
- 4. **For destructive actions** (delete, debit), always confirm with the user first.
276
- 5. **Cart evaluation** returns detailed match info — check `evaluatedPromotions` for per-promotion condition/computation results and `warnings` for coupon validation issues.
474
+ ```json
475
+ skill_submit_feedback({
476
+ "toolName": "promotions.create",
477
+ "action": "created",
478
+ "context": "Building a BOGO promotion for shoes",
479
+ "outcome": "success",
480
+ "details": "Worked on first try with category filter"
481
+ })
482
+ ```
package/dist/index.js CHANGED
@@ -13662,11 +13662,11 @@ class StdioServerTransport {
13662
13662
 
13663
13663
  // src/index.ts
13664
13664
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
13665
- var SKILL_CONTENT;
13665
+ var FALLBACK_SKILL_CONTENT;
13666
13666
  try {
13667
- SKILL_CONTENT = readFileSync(resolve(__dirname2, "../SKILL.md"), "utf-8");
13667
+ FALLBACK_SKILL_CONTENT = readFileSync(resolve(__dirname2, "../SKILL.md"), "utf-8");
13668
13668
  } catch {
13669
- SKILL_CONTENT = "Store Layer MCP Server — manage loyalty programs, promotions, wallets, users, events, and referrals. Use the available tools to interact with your project.";
13669
+ FALLBACK_SKILL_CONTENT = "Store Layer MCP Server — manage loyalty programs, promotions, wallets, users, events, and referrals. Use the available tools to interact with your project.";
13670
13670
  }
13671
13671
  var API_URL = (process.env.STORE_LAYER_API_URL || "https://api.storelayer.io").replace(/\/$/, "");
13672
13672
  var API_KEY = process.env.STORE_LAYER_API_KEY;
@@ -13678,7 +13678,7 @@ function apiHeaders() {
13678
13678
  };
13679
13679
  }
13680
13680
  async function fetchToolManifest() {
13681
- const url = `${API_URL}/api/projects/${PROJECT_ID}/internal-tools`;
13681
+ const url = `${API_URL}/projects/${PROJECT_ID}/internal-tools`;
13682
13682
  const res = await fetch(url, { headers: apiHeaders() });
13683
13683
  if (!res.ok) {
13684
13684
  const body = await res.text().catch(() => "");
@@ -13687,8 +13687,26 @@ async function fetchToolManifest() {
13687
13687
  const json = await res.json();
13688
13688
  return json.data.tools;
13689
13689
  }
13690
+ async function fetchSkillContent() {
13691
+ try {
13692
+ const url = `${API_URL}/projects/${PROJECT_ID}/internal-skill`;
13693
+ const res = await fetch(url, { headers: apiHeaders() });
13694
+ if (!res.ok) {
13695
+ console.error(`Skill content fetch failed (${res.status}), using fallback`);
13696
+ return { content: FALLBACK_SKILL_CONTENT, version: "local" };
13697
+ }
13698
+ const json = await res.json();
13699
+ if (json.success && json.data?.content) {
13700
+ return { content: json.data.content, version: json.data.version };
13701
+ }
13702
+ return { content: FALLBACK_SKILL_CONTENT, version: "local" };
13703
+ } catch (error2) {
13704
+ console.error("Failed to fetch skill content, using fallback:", error2);
13705
+ return { content: FALLBACK_SKILL_CONTENT, version: "local" };
13706
+ }
13707
+ }
13690
13708
  async function executeTool(toolName, params, userId) {
13691
- const url = `${API_URL}/api/projects/${PROJECT_ID}/internal-tools/${toolName}/execute`;
13709
+ const url = `${API_URL}/projects/${PROJECT_ID}/internal-tools/${toolName}/execute`;
13692
13710
  const body = { params };
13693
13711
  if (userId)
13694
13712
  body.userId = userId;
@@ -13739,8 +13757,12 @@ async function main() {
13739
13757
  process.exit(1);
13740
13758
  }
13741
13759
  console.error(`Connecting to Store Layer API at ${API_URL}...`);
13742
- const manifest = await fetchToolManifest();
13760
+ const [manifest, skill] = await Promise.all([
13761
+ fetchToolManifest(),
13762
+ fetchSkillContent()
13763
+ ]);
13743
13764
  console.error(`Loaded ${manifest.length} tools from Store Layer`);
13765
+ console.error(`Skill guide version: ${skill.version}`);
13744
13766
  const mcpToRegistry = new Map;
13745
13767
  const mcpToManifest = new Map;
13746
13768
  for (const tool of manifest) {
@@ -13748,7 +13770,7 @@ async function main() {
13748
13770
  mcpToRegistry.set(mcpName, tool.name);
13749
13771
  mcpToManifest.set(mcpName, tool);
13750
13772
  }
13751
- const server = new Server({ name: "store-layer", version: "0.1.0" }, { capabilities: { tools: {}, prompts: {} } });
13773
+ const server = new Server({ name: "store-layer", version: "0.2.0" }, { capabilities: { tools: {}, prompts: {} } });
13752
13774
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
13753
13775
  tools: manifest.map((tool) => ({
13754
13776
  name: toMcpName(tool.name),
@@ -13799,7 +13821,7 @@ async function main() {
13799
13821
  prompts: [
13800
13822
  {
13801
13823
  name: "store-layer-guide",
13802
- description: "Comprehensive guide for using Store Layer tools — covers all domains (promotions, wallets, users, events, referrals, rules), with examples and best practices."
13824
+ description: `Comprehensive guide for using Store Layer tools (v${skill.version}) — covers all domains (promotions, wallets, users, events, referrals, rules), with recipes and best practices.`
13803
13825
  }
13804
13826
  ]
13805
13827
  }));
@@ -13808,13 +13830,13 @@ async function main() {
13808
13830
  throw new Error(`Unknown prompt: ${request.params.name}`);
13809
13831
  }
13810
13832
  return {
13811
- description: "Store Layer MCP skill guide",
13833
+ description: `Store Layer MCP skill guide (v${skill.version})`,
13812
13834
  messages: [
13813
13835
  {
13814
13836
  role: "user",
13815
13837
  content: {
13816
13838
  type: "text",
13817
- text: SKILL_CONTENT
13839
+ text: skill.content
13818
13840
  }
13819
13841
  }
13820
13842
  ]
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
- "name": "@storelayer/mcp-server",
3
- "version": "0.2.0",
4
- "description": "MCP server for Store Layer — manage loyalty programs, promotions, wallets, and more from Claude Desktop, Claude Code, or Cursor.",
5
- "type": "module",
6
- "bin": {
7
- "store-layer-mcp-server": "dist/index.js"
8
- },
9
- "files": [
10
- "dist",
11
- "SKILL.md"
12
- ],
13
- "scripts": {
14
- "build": "bun build src/index.ts --target=node --outdir=dist --format=esm",
15
- "dev": "bun run src/index.ts",
16
- "start": "node dist/index.js",
17
- "prepublishOnly": "bun run build",
18
- "publish:npm": "bun run build && npm publish --access public"
19
- },
20
- "dependencies": {
21
- "@modelcontextprotocol/sdk": "^1.12.1"
22
- },
23
- "devDependencies": {
24
- "@types/node": "^22.0.0",
25
- "typescript": "^5.7.0"
26
- }
2
+ "name": "@storelayer/mcp-server",
3
+ "version": "0.3.0",
4
+ "description": "MCP server for Store Layer — manage loyalty programs, promotions, wallets, and more from Claude Desktop, Claude Code, or Cursor.",
5
+ "type": "module",
6
+ "bin": {
7
+ "store-layer-mcp-server": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "SKILL.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "bun build src/index.ts --target=node --outdir=dist --format=esm",
15
+ "dev": "bun run src/index.ts",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "bun run build",
18
+ "publish:npm": "bun run build && npm publish --access public"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.12.1"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "typescript": "^5.7.0"
26
+ }
27
27
  }