@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.
- package/SKILL.md +405 -199
- package/dist/index.js +32 -10
- package/package.json +25 -25
package/SKILL.md
CHANGED
|
@@ -1,276 +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_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
|
-
##
|
|
53
|
+
## Recipes
|
|
122
54
|
|
|
123
|
-
###
|
|
55
|
+
### Recipe 1: Points-for-Purchase (Earn 1 point per dollar)
|
|
124
56
|
|
|
125
|
-
|
|
57
|
+
**Domains:** Rules + Wallet
|
|
126
58
|
|
|
127
|
-
**
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
104
|
+
```json
|
|
105
|
+
wallet_get_balance({ "userId": "test-user-123" })
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
144
109
|
|
|
145
|
-
|
|
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
|
-
**
|
|
112
|
+
**Domains:** Promotions
|
|
156
113
|
|
|
157
|
-
|
|
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": "
|
|
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
|
-
|
|
128
|
+
**Step 2:** Test with a cart:
|
|
129
|
+
|
|
173
130
|
```json
|
|
174
|
-
{
|
|
175
|
-
"
|
|
176
|
-
|
|
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": [
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
"
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
244
|
+
**Step 2:** Create referral code for a user:
|
|
221
245
|
|
|
222
|
-
|
|
246
|
+
```json
|
|
247
|
+
referral_create_code({ "referrerId": "user-123" })
|
|
248
|
+
```
|
|
223
249
|
|
|
224
|
-
**
|
|
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": "
|
|
233
|
-
"eventType": "purchase",
|
|
253
|
+
project_add_rule({
|
|
254
|
+
"name": "Referral Reward",
|
|
234
255
|
"conditions": {
|
|
235
|
-
"conditions": [
|
|
256
|
+
"conditions": [
|
|
257
|
+
{ "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "referral.completed", "rightType": "string" }
|
|
258
|
+
],
|
|
236
259
|
"combinator": "AND"
|
|
237
260
|
},
|
|
238
|
-
"actions": [
|
|
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
|
-
###
|
|
279
|
+
### Recipe 7: Stacking Promotions (Member discount + seasonal sale)
|
|
245
280
|
|
|
246
|
-
|
|
281
|
+
**Domains:** Promotions (stacking)
|
|
247
282
|
|
|
248
|
-
|
|
283
|
+
Create two stackable promotions:
|
|
249
284
|
|
|
250
|
-
**Event payload example:**
|
|
251
285
|
```json
|
|
252
|
-
|
|
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
|
-
|
|
452
|
+
## Common Mistakes
|
|
258
453
|
|
|
259
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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;
|
|
@@ -13678,7 +13678,7 @@ function apiHeaders() {
|
|
|
13678
13678
|
};
|
|
13679
13679
|
}
|
|
13680
13680
|
async function fetchToolManifest() {
|
|
13681
|
-
const url = `${API_URL}/
|
|
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}/
|
|
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
|
|
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
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
}
|