@storelayer/mcp-server 0.2.1 → 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 -197
  2. package/dist/index.js +30 -8
  3. package/package.json +1 -1
package/SKILL.md CHANGED
@@ -1,274 +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_evaluate_cart` | **Evaluate a cart against active promotions** |
78
-
79
- ### Support (`support`)
80
- | Tool | Description |
81
- |------|-------------|
82
- | `support_get_ticket` | Get a ticket by ID |
83
- | `support_list_tickets` | List tickets with filters |
84
- | `support_get_stats` | Aggregate ticket stats |
85
- | `support_create_ticket` | Create a ticket |
86
- | `support_update_ticket` | Update a ticket |
87
-
88
- ### Surveys (`surveys`)
89
- | Tool | Description |
90
- |------|-------------|
91
- | `surveys_get` | Get a survey by ID |
92
- | `surveys_list` | List surveys |
93
- | `surveys_list_responses` | List responses for a survey |
94
- | `surveys_get_stats` | Survey analytics |
95
- | `surveys_create` | Create a survey |
96
- | `surveys_submit_response` | Submit a response |
97
-
98
- ### Workflows (`workflows`)
99
- | Tool | Description |
100
- |------|-------------|
101
- | `workflows_list` | List workflow executions |
102
- | `workflows_get` | Get a workflow execution with steps |
103
-
104
- ### Stores (`stores`)
105
- | Tool | Description |
106
- |------|-------------|
107
- | `stores_get_store` / `stores_list_stores` | Read stores |
108
- | `stores_create_store` / `stores_update_store` / `stores_remove_store` | Manage stores |
109
- | `stores_get_facility` / `stores_list_facilities` | Read facilities |
110
- | `stores_create_facility` / `stores_update_facility` / `stores_remove_facility` | Manage facilities |
111
-
112
- ### Resources (`resources`)
113
- | Tool | Description |
114
- |------|-------------|
115
- | `resources_list` / `resources_get` | Read resource definitions |
116
- | `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
117
50
 
118
51
  ---
119
52
 
120
- ## Domain Knowledge
53
+ ## Recipes
121
54
 
122
- ### Promotions Engine
55
+ ### Recipe 1: Points-for-Purchase (Earn 1 point per dollar)
123
56
 
124
- 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
125
58
 
126
- **Script environment:**
127
- - `$('cart')` — full cart object. `$('cart').items` is the items array. Each item: `{ id, price, quantity, category, tags, ...custom }`
128
- - `$('cart').total` — cart total
129
- - `$('user')` — current user (if userId provided)
130
- - `$('couponCodes')` — applied coupon codes
59
+ **Step 1:** Create the rule:
131
60
 
132
- **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
+ ```
133
101
 
134
- **Condition system:**
135
- - Structure: `{ conditions: [{ leftValue, operator, rightValue, rightType }], combinator: "AND" | "OR" }`
136
- - Fields: `cart.total`, `cart.itemCount`, `cart.uniqueItemCount`, `cart.items[0].price`, etc.
137
- - 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):
138
103
 
139
- **Stacking modes:**
140
- - `stackable` combines with other promotions (default)
141
- - `exclusive` — highest priority wins, all others excluded
142
- - `exclusive_group` — highest priority within its group wins
104
+ ```json
105
+ wallet_get_balance({ "userId": "test-user-123" })
106
+ ```
107
+
108
+ ---
143
109
 
144
- **Cart formats:**
145
- - **Standard:** Items have `quantity` field. Engine expands internally.
146
- ```json
147
- { "items": [{ "id": "muffin-001", "price": 3.50, "quantity": 2 }] }
148
- ```
149
- - **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.
150
- ```json
151
- { "items": [{ "id": "sub-1", "sku": "muffin-001", "price": 3.50 }, { "id": "sub-2", "sku": "muffin-001", "price": 3.50 }] }
152
- ```
110
+ ### Recipe 2: Percentage Discount (10% off everything)
153
111
 
154
- **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
155
113
 
156
- #### Promotion Examples
114
+ **Step 1:** Create promotion in draft:
157
115
 
158
- **10% off all items:**
159
116
  ```json
160
- {
117
+ promotions_create({
161
118
  "name": "10% Off Everything",
162
- "status": "active",
119
+ "status": "draft",
163
120
  "conditions": { "conditions": [], "combinator": "AND" },
164
121
  "itemsDiscountComputation": {
165
122
  "script": "const items = $('cart').items;\nreturn items.map(item => ({ id: item.id, amount: item.price * 0.10 }));",
166
123
  "language": "javascript"
167
124
  }
168
- }
125
+ })
169
126
  ```
170
127
 
171
- **$5 off orders over $50:**
128
+ **Step 2:** Test with a cart:
129
+
172
130
  ```json
173
- {
174
- "name": "$5 Off Over $50",
175
- "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",
176
157
  "conditions": {
177
- "conditions": [{ "leftValue": "cart.total", "operator": "gte", "rightValue": 50, "rightType": "number" }],
158
+ "conditions": [
159
+ { "leftValue": "cart.total", "operator": "gte", "rightValue": 50, "rightType": "number" }
160
+ ],
178
161
  "combinator": "AND"
179
162
  },
180
163
  "itemsDiscountComputation": {
181
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 }));",
182
165
  "language": "javascript"
183
166
  }
184
- }
167
+ })
185
168
  ```
186
169
 
187
- **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
+
188
178
  ```json
189
- {
179
+ promotions_create({
180
+ "name": "BOGO Shoes",
181
+ "status": "draft",
182
+ "conditions": { "conditions": [], "combinator": "AND" },
190
183
  "itemsDiscountComputation": {
191
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 }];",
192
185
  "language": "javascript"
193
186
  }
194
- }
187
+ })
195
188
  ```
196
189
 
197
- #### 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:
198
199
 
199
- Use `promotions_evaluate_cart` with:
200
200
  ```json
201
- {
202
- "cart": {
203
- "items": [
204
- { "id": "item-1", "price": 25.00, "quantity": 2, "category": "shoes" },
205
- { "id": "item-2", "price": 15.00, "quantity": 1, "category": "accessories" }
206
- ]
207
- },
208
- "userId": "user_123",
209
- "couponCodes": ["SAVE10"]
210
- }
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
+ })
211
221
  ```
212
222
 
213
- Response includes: `summary` (originalTotal, totalDiscount, finalTotal, appliedCount), `items` (per-item discounts), `applied` (matched promotions with discounts), `notApplied` (unmatched promotions with reasons, human-readable messages, and suggestions), `discounts` (flat discount list), `warnings`, and `usageRecords` (when not dry run).
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
+ ```
214
231
 
215
232
  ---
216
233
 
217
- ### 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
+ ```
218
243
 
219
- 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:
220
245
 
221
- **Condition expressions** use Handlebars syntax: `{{ event.type }}`, `{{ event.payload.amount }}`, `{{ user.email }}`, `{{ history.amount }}`
246
+ ```json
247
+ referral_create_code({ "referrerId": "user-123" })
248
+ ```
222
249
 
223
- **Action types:**
224
- - `reward` — `{ assetType: "points", amount: 10, description: "Purchase reward" }`. Dynamic: `amount: "{{ event.amount }}"`
225
- - `integration` — `{ integrationId: "intg_xxx" }` — triggers webhook/Telegram/Slack
226
- - `apply_referral` / `complete_referral` / `mark_code_used` — referral lifecycle actions
250
+ **Step 3:** Set up a rule to reward on completed referral:
227
251
 
228
- **Rule example — 1 point per dollar spent:**
229
252
  ```json
230
- {
231
- "name": "Dollar-to-Points",
253
+ project_add_rule({
254
+ "name": "Referral Reward",
232
255
  "conditions": {
233
- "conditions": [{ "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "purchase", "rightType": "string" }],
256
+ "conditions": [
257
+ { "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "referral.completed", "rightType": "string" }
258
+ ],
234
259
  "combinator": "AND"
235
260
  },
236
- "actions": [{ "type": "reward", "config": { "assetType": "points", "amount": "{{ event.payload.amount }}", "description": "1 point per dollar" } }]
237
- }
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
+ })
238
275
  ```
239
276
 
240
277
  ---
241
278
 
242
- ### Events & Ingestion
279
+ ### Recipe 7: Stacking Promotions (Member discount + seasonal sale)
243
280
 
244
- 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)
245
282
 
246
- **Processing flow:** Event stored → queued → resources resolved → conditions evaluated → actions executed → event marked processed.
283
+ Create two stackable promotions:
247
284
 
248
- **Event payload example:**
249
285
  ```json
250
- { "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
+ })
251
313
  ```
252
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
+
253
450
  ---
254
451
 
255
- ### Resources
452
+ ## Common Mistakes
256
453
 
257
- Resources provide runtime context for rule evaluation. Types:
258
- - **event** — incoming event data
259
- - **http** — external REST API call
260
- - **database** — PostgreSQL query via integration
261
- - **internal** — Store Layer entity data (users, wallets, transactions via registry tools)
262
- - **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.
263
455
 
264
- 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.
265
467
 
266
468
  ---
267
469
 
268
- ## 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.
269
473
 
270
- 1. **Start by exploring** — use `promotions_get_aggregate_stats`, `project_get_config`, or list tools to understand the current state before making changes.
271
- 2. **Test before activating** — create promotions in `draft` status, use `promotions_evaluate_cart` to test with sample carts, then set to `active`.
272
- 3. **Use `project_test_conditions`** to validate rule conditions against sample data before saving.
273
- 4. **For destructive actions** (delete, debit), always confirm with the user first.
274
- 5. **Cart evaluation** returns detailed match info — check `applied` for matched promotions with discounts, `notApplied` for unmatched promotions with reasons/suggestions, 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;
@@ -13687,6 +13687,24 @@ 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
13709
  const url = `${API_URL}/projects/${PROJECT_ID}/internal-tools/${toolName}/execute`;
13692
13710
  const body = { params };
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@storelayer/mcp-server",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "MCP server for Store Layer — manage loyalty programs, promotions, wallets, and more from Claude Desktop, Claude Code, or Cursor.",
5
5
  "type": "module",
6
6
  "bin": {