@storelayer/mcp-server 0.3.0 → 0.4.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/dist/index.js +180 -49
  2. package/package.json +2 -3
  3. package/SKILL.md +0 -482
package/dist/index.js CHANGED
@@ -6516,11 +6516,6 @@ var require_dist = __commonJS((exports, module) => {
6516
6516
  exports.default = formatsPlugin;
6517
6517
  });
6518
6518
 
6519
- // src/index.ts
6520
- import { readFileSync } from "node:fs";
6521
- import { resolve, dirname } from "node:path";
6522
- import { fileURLToPath } from "node:url";
6523
-
6524
6519
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/core/core.js
6525
6520
  var NEVER = Object.freeze({
6526
6521
  status: "aborted"
@@ -13661,16 +13656,92 @@ class StdioServerTransport {
13661
13656
  }
13662
13657
 
13663
13658
  // src/index.ts
13664
- var __dirname2 = dirname(fileURLToPath(import.meta.url));
13665
- var FALLBACK_SKILL_CONTENT;
13666
- try {
13667
- FALLBACK_SKILL_CONTENT = readFileSync(resolve(__dirname2, "../SKILL.md"), "utf-8");
13668
- } catch {
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
- }
13671
13659
  var API_URL = (process.env.STORE_LAYER_API_URL || "https://api.storelayer.io").replace(/\/$/, "");
13672
13660
  var API_KEY = process.env.STORE_LAYER_API_KEY;
13673
13661
  var PROJECT_ID = process.env.STORE_LAYER_PROJECT_ID;
13662
+ var SKILLS_REPO = "storelayer/skills";
13663
+ var SKILLS_BASE_PATH = "skills/storelayer";
13664
+ var SKILL_REFERENCES = [
13665
+ {
13666
+ name: "architecture",
13667
+ description: "Platform architecture and patterns",
13668
+ path: "references/architecture.md"
13669
+ },
13670
+ {
13671
+ name: "wallet",
13672
+ description: "Wallet/ledger system reference",
13673
+ path: "references/wallet.md"
13674
+ },
13675
+ {
13676
+ name: "promotions",
13677
+ description: "Promotion engine reference",
13678
+ path: "references/promotions.md"
13679
+ },
13680
+ {
13681
+ name: "referral",
13682
+ description: "Referral system reference",
13683
+ path: "references/referral.md"
13684
+ },
13685
+ {
13686
+ name: "events",
13687
+ description: "Events and rules reference",
13688
+ path: "references/events.md"
13689
+ },
13690
+ {
13691
+ name: "external-users",
13692
+ description: "Customer management reference",
13693
+ path: "references/external-users.md"
13694
+ },
13695
+ {
13696
+ name: "discount-scripts",
13697
+ description: "Discount script reference",
13698
+ path: "references/discount-scripts.md"
13699
+ },
13700
+ {
13701
+ name: "conditions",
13702
+ description: "Condition engine reference",
13703
+ path: "references/conditions.md"
13704
+ },
13705
+ {
13706
+ name: "mcp-tools",
13707
+ description: "All MCP tools reference",
13708
+ path: "references/mcp-tools.md"
13709
+ }
13710
+ ];
13711
+ var SKILL_AGENTS = [
13712
+ {
13713
+ name: "loyalty-builder",
13714
+ description: "Design and build complete loyalty programs",
13715
+ path: "agents/loyalty-builder.md"
13716
+ },
13717
+ {
13718
+ name: "promo-engineer",
13719
+ description: "Build promotions, discount scripts, and coupon campaigns",
13720
+ path: "agents/promo-engineer.md"
13721
+ },
13722
+ {
13723
+ name: "integration-dev",
13724
+ description: "Set up event ingestion and external integrations",
13725
+ path: "agents/integration-dev.md"
13726
+ }
13727
+ ];
13728
+ var SKILL_TOOLS = [
13729
+ {
13730
+ name: "setup-project",
13731
+ description: "Step-by-step new project setup",
13732
+ path: "tools/setup-project.md"
13733
+ },
13734
+ {
13735
+ name: "test-promotion",
13736
+ description: "Test promotions before activation",
13737
+ path: "tools/test-promotion.md"
13738
+ },
13739
+ {
13740
+ name: "debug-rules",
13741
+ description: "Debug rules and promotions",
13742
+ path: "tools/debug-rules.md"
13743
+ }
13744
+ ];
13674
13745
  function apiHeaders() {
13675
13746
  return {
13676
13747
  Authorization: `Bearer ${API_KEY}`,
@@ -13687,24 +13758,6 @@ async function fetchToolManifest() {
13687
13758
  const json = await res.json();
13688
13759
  return json.data.tools;
13689
13760
  }
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
- }
13708
13761
  async function executeTool(toolName, params, userId) {
13709
13762
  const url = `${API_URL}/projects/${PROJECT_ID}/internal-tools/${toolName}/execute`;
13710
13763
  const body = { params };
@@ -13724,6 +13777,31 @@ async function executeTool(toolName, params, userId) {
13724
13777
  throw new Error(json.error || "Tool execution failed");
13725
13778
  return json.data;
13726
13779
  }
13780
+ var contentCache = new Map;
13781
+ var CACHE_TTL_MS = 15 * 60 * 1000;
13782
+ async function fetchSkillFile(relativePath) {
13783
+ const cacheKey = relativePath;
13784
+ const cached2 = contentCache.get(cacheKey);
13785
+ if (cached2 && Date.now() - cached2.fetchedAt < CACHE_TTL_MS) {
13786
+ return cached2.content;
13787
+ }
13788
+ const url = `https://raw.githubusercontent.com/${SKILLS_REPO}/main/${SKILLS_BASE_PATH}/${relativePath}`;
13789
+ const res = await fetch(url);
13790
+ if (!res.ok) {
13791
+ throw new Error(`Failed to fetch ${relativePath} from GitHub (${res.status})`);
13792
+ }
13793
+ const content = await res.text();
13794
+ contentCache.set(cacheKey, { content, fetchedAt: Date.now() });
13795
+ return content;
13796
+ }
13797
+ async function fetchSkillGuide() {
13798
+ try {
13799
+ return await fetchSkillFile("SKILL.md");
13800
+ } catch (error2) {
13801
+ console.error("Failed to fetch SKILL.md from GitHub:", error2);
13802
+ return "Storelayer MCP Server — manage loyalty programs, promotions, wallets, users, events, and referrals. Use the available tools to interact with your project. Skill guide unavailable (GitHub unreachable).";
13803
+ }
13804
+ }
13727
13805
  function buildInputSchema(tool) {
13728
13806
  const schema = structuredClone(tool.parameters);
13729
13807
  if (!schema.type)
@@ -13757,12 +13835,12 @@ async function main() {
13757
13835
  process.exit(1);
13758
13836
  }
13759
13837
  console.error(`Connecting to Store Layer API at ${API_URL}...`);
13760
- const [manifest, skill] = await Promise.all([
13838
+ const [manifest, skillGuide] = await Promise.all([
13761
13839
  fetchToolManifest(),
13762
- fetchSkillContent()
13840
+ fetchSkillGuide()
13763
13841
  ]);
13764
13842
  console.error(`Loaded ${manifest.length} tools from Store Layer`);
13765
- console.error(`Skill guide version: ${skill.version}`);
13843
+ console.error(`Skill guide loaded from GitHub (${SKILLS_REPO})`);
13766
13844
  const mcpToRegistry = new Map;
13767
13845
  const mcpToManifest = new Map;
13768
13846
  for (const tool of manifest) {
@@ -13770,7 +13848,12 @@ async function main() {
13770
13848
  mcpToRegistry.set(mcpName, tool.name);
13771
13849
  mcpToManifest.set(mcpName, tool);
13772
13850
  }
13773
- const server = new Server({ name: "store-layer", version: "0.2.0" }, { capabilities: { tools: {}, prompts: {} } });
13851
+ const allSkillFiles = [
13852
+ ...SKILL_REFERENCES.map((f) => ({ ...f, category: "reference" })),
13853
+ ...SKILL_AGENTS.map((f) => ({ ...f, category: "agent" })),
13854
+ ...SKILL_TOOLS.map((f) => ({ ...f, category: "tool" }))
13855
+ ];
13856
+ const server = new Server({ name: "store-layer", version: "0.4.0" }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
13774
13857
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
13775
13858
  tools: manifest.map((tool) => ({
13776
13859
  name: toMcpName(tool.name),
@@ -13821,26 +13904,74 @@ async function main() {
13821
13904
  prompts: [
13822
13905
  {
13823
13906
  name: "store-layer-guide",
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.`
13825
- }
13907
+ description: "Comprehensive guide for using Storelayer tools — covers all domains (promotions, wallets, users, events, referrals, rules), with recipes and best practices."
13908
+ },
13909
+ ...SKILL_AGENTS.map((agent) => ({
13910
+ name: agent.name,
13911
+ description: agent.description
13912
+ }))
13826
13913
  ]
13827
13914
  }));
13828
13915
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
13829
- if (request.params.name !== "store-layer-guide") {
13830
- throw new Error(`Unknown prompt: ${request.params.name}`);
13916
+ const promptName = request.params.name;
13917
+ if (promptName === "store-layer-guide") {
13918
+ return {
13919
+ description: "Storelayer MCP skill guide",
13920
+ messages: [
13921
+ {
13922
+ role: "user",
13923
+ content: { type: "text", text: skillGuide }
13924
+ }
13925
+ ]
13926
+ };
13831
13927
  }
13832
- return {
13833
- description: `Store Layer MCP skill guide (v${skill.version})`,
13834
- messages: [
13835
- {
13836
- role: "user",
13837
- content: {
13838
- type: "text",
13839
- text: skill.content
13928
+ const agent = SKILL_AGENTS.find((a) => a.name === promptName);
13929
+ if (agent) {
13930
+ try {
13931
+ const content = await fetchSkillFile(agent.path);
13932
+ return {
13933
+ description: agent.description,
13934
+ messages: [
13935
+ {
13936
+ role: "user",
13937
+ content: { type: "text", text: content }
13938
+ }
13939
+ ]
13940
+ };
13941
+ } catch (error2) {
13942
+ throw new Error(`Failed to fetch agent ${agent.name}: ${error2 instanceof Error ? error2.message : String(error2)}`);
13943
+ }
13944
+ }
13945
+ throw new Error(`Unknown prompt: ${promptName}`);
13946
+ });
13947
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
13948
+ resources: allSkillFiles.map((file) => ({
13949
+ uri: `storelayer://${file.category}/${file.name}`,
13950
+ name: file.name,
13951
+ description: file.description,
13952
+ mimeType: "text/markdown"
13953
+ }))
13954
+ }));
13955
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
13956
+ const uri = request.params.uri;
13957
+ const file = allSkillFiles.find((f) => `storelayer://${f.category}/${f.name}` === uri);
13958
+ if (!file) {
13959
+ throw new Error(`Unknown resource: ${uri}`);
13960
+ }
13961
+ try {
13962
+ const content = await fetchSkillFile(file.path);
13963
+ return {
13964
+ contents: [
13965
+ {
13966
+ uri,
13967
+ mimeType: "text/markdown",
13968
+ text: content
13840
13969
  }
13841
- }
13842
- ]
13843
- };
13970
+ ]
13971
+ };
13972
+ } catch (error2) {
13973
+ throw new Error(`Failed to fetch ${file.path}: ${error2 instanceof Error ? error2.message : String(error2)}`);
13974
+ }
13844
13975
  });
13845
13976
  const transport = new StdioServerTransport;
13846
13977
  await server.connect(transport);
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@storelayer/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": {
7
7
  "store-layer-mcp-server": "dist/index.js"
8
8
  },
9
9
  "files": [
10
- "dist",
11
- "SKILL.md"
10
+ "dist"
12
11
  ],
13
12
  "scripts": {
14
13
  "build": "bun build src/index.ts --target=node --outdir=dist --format=esm",
package/SKILL.md DELETED
@@ -1,482 +0,0 @@
1
- # Store Layer — AI Skill Guide
2
-
3
- You have access to the **Store Layer** MCP server. This guide helps you build loyalty programs, promotions, and customer engagement systems effectively.
4
-
5
- ## Pipeline: How to Build Loyalty Programs
6
-
7
- **Always follow this pipeline** when building loyalty features. Do not skip steps.
8
-
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.
36
-
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
50
-
51
- ---
52
-
53
- ## Recipes
54
-
55
- ### Recipe 1: Points-for-Purchase (Earn 1 point per dollar)
56
-
57
- **Domains:** Rules + Wallet
58
-
59
- **Step 1:** Create the rule:
60
-
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
- ```
101
-
102
- **Step 3:** Verify wallet (for a test user):
103
-
104
- ```json
105
- wallet_get_balance({ "userId": "test-user-123" })
106
- ```
107
-
108
- ---
109
-
110
- ### Recipe 2: Percentage Discount (10% off everything)
111
-
112
- **Domains:** Promotions
113
-
114
- **Step 1:** Create promotion in draft:
115
-
116
- ```json
117
- promotions_create({
118
- "name": "10% Off Everything",
119
- "status": "draft",
120
- "conditions": { "conditions": [], "combinator": "AND" },
121
- "itemsDiscountComputation": {
122
- "script": "const items = $('cart').items;\nreturn items.map(item => ({ id: item.id, amount: item.price * 0.10 }));",
123
- "language": "javascript"
124
- }
125
- })
126
- ```
127
-
128
- **Step 2:** Test with a cart:
129
-
130
- ```json
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",
157
- "conditions": {
158
- "conditions": [
159
- { "leftValue": "cart.total", "operator": "gte", "rightValue": 50, "rightType": "number" }
160
- ],
161
- "combinator": "AND"
162
- },
163
- "itemsDiscountComputation": {
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 }));",
165
- "language": "javascript"
166
- }
167
- })
168
- ```
169
-
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
-
178
- ```json
179
- promotions_create({
180
- "name": "BOGO Shoes",
181
- "status": "draft",
182
- "conditions": { "conditions": [], "combinator": "AND" },
183
- "itemsDiscountComputation": {
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 }];",
185
- "language": "javascript"
186
- }
187
- })
188
- ```
189
-
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
-
200
- ```json
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
- })
221
- ```
222
-
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
- ```
231
-
232
- ---
233
-
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
- ```
243
-
244
- **Step 2:** Create referral code for a user:
245
-
246
- ```json
247
- referral_create_code({ "referrerId": "user-123" })
248
- ```
249
-
250
- **Step 3:** Set up a rule to reward on completed referral:
251
-
252
- ```json
253
- project_add_rule({
254
- "name": "Referral Reward",
255
- "conditions": {
256
- "conditions": [
257
- { "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "referral.completed", "rightType": "string" }
258
- ],
259
- "combinator": "AND"
260
- },
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
- })
275
- ```
276
-
277
- ---
278
-
279
- ### Recipe 7: Stacking Promotions (Member discount + seasonal sale)
280
-
281
- **Domains:** Promotions (stacking)
282
-
283
- Create two stackable promotions:
284
-
285
- ```json
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
- })
313
- ```
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
-
450
- ---
451
-
452
- ## Common Mistakes
453
-
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.
455
-
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.
467
-
468
- ---
469
-
470
- ## Feedback
471
-
472
- After building a loyalty program, use `skill_submit_feedback` to report the outcome. This helps improve this guide over time.
473
-
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
- ```