@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.
- package/SKILL.md +405 -197
- package/dist/index.js +30 -8
- package/package.json +1 -1
package/SKILL.md
CHANGED
|
@@ -1,274 +1,482 @@
|
|
|
1
|
-
# Store Layer
|
|
1
|
+
# Store Layer — AI Skill Guide
|
|
2
2
|
|
|
3
|
-
You have access to the **Store Layer** MCP server
|
|
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
|
|
5
|
+
## Pipeline: How to Build Loyalty Programs
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
##
|
|
53
|
+
## Recipes
|
|
121
54
|
|
|
122
|
-
###
|
|
55
|
+
### Recipe 1: Points-for-Purchase (Earn 1 point per dollar)
|
|
123
56
|
|
|
124
|
-
|
|
57
|
+
**Domains:** Rules + Wallet
|
|
125
58
|
|
|
126
|
-
**
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
104
|
+
```json
|
|
105
|
+
wallet_get_balance({ "userId": "test-user-123" })
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
143
109
|
|
|
144
|
-
|
|
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
|
-
**
|
|
112
|
+
**Domains:** Promotions
|
|
155
113
|
|
|
156
|
-
|
|
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": "
|
|
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
|
-
|
|
128
|
+
**Step 2:** Test with a cart:
|
|
129
|
+
|
|
172
130
|
```json
|
|
173
|
-
{
|
|
174
|
-
"
|
|
175
|
-
|
|
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": [
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
244
|
+
**Step 2:** Create referral code for a user:
|
|
220
245
|
|
|
221
|
-
|
|
246
|
+
```json
|
|
247
|
+
referral_create_code({ "referrerId": "user-123" })
|
|
248
|
+
```
|
|
222
249
|
|
|
223
|
-
**
|
|
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": "
|
|
253
|
+
project_add_rule({
|
|
254
|
+
"name": "Referral Reward",
|
|
232
255
|
"conditions": {
|
|
233
|
-
"conditions": [
|
|
256
|
+
"conditions": [
|
|
257
|
+
{ "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "referral.completed", "rightType": "string" }
|
|
258
|
+
],
|
|
234
259
|
"combinator": "AND"
|
|
235
260
|
},
|
|
236
|
-
"actions": [
|
|
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
|
-
###
|
|
279
|
+
### Recipe 7: Stacking Promotions (Member discount + seasonal sale)
|
|
243
280
|
|
|
244
|
-
|
|
281
|
+
**Domains:** Promotions (stacking)
|
|
245
282
|
|
|
246
|
-
|
|
283
|
+
Create two stackable promotions:
|
|
247
284
|
|
|
248
|
-
**Event payload example:**
|
|
249
285
|
```json
|
|
250
|
-
|
|
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
|
-
|
|
452
|
+
## Common Mistakes
|
|
256
453
|
|
|
257
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
13665
|
+
var FALLBACK_SKILL_CONTENT;
|
|
13666
13666
|
try {
|
|
13667
|
-
|
|
13667
|
+
FALLBACK_SKILL_CONTENT = readFileSync(resolve(__dirname2, "../SKILL.md"), "utf-8");
|
|
13668
13668
|
} catch {
|
|
13669
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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:
|
|
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:
|
|
13839
|
+
text: skill.content
|
|
13818
13840
|
}
|
|
13819
13841
|
}
|
|
13820
13842
|
]
|
package/package.json
CHANGED