@storelayer/mcp-server 0.2.1 → 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 +182 -29
  2. package/package.json +2 -3
  3. package/SKILL.md +0 -274
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 SKILL_CONTENT;
13666
- try {
13667
- SKILL_CONTENT = readFileSync(resolve(__dirname2, "../SKILL.md"), "utf-8");
13668
- } catch {
13669
- SKILL_CONTENT = "Store Layer MCP Server — manage loyalty programs, promotions, wallets, users, events, and referrals. Use the available tools to interact with your project.";
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}`,
@@ -13706,6 +13777,31 @@ async function executeTool(toolName, params, userId) {
13706
13777
  throw new Error(json.error || "Tool execution failed");
13707
13778
  return json.data;
13708
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
+ }
13709
13805
  function buildInputSchema(tool) {
13710
13806
  const schema = structuredClone(tool.parameters);
13711
13807
  if (!schema.type)
@@ -13739,8 +13835,12 @@ async function main() {
13739
13835
  process.exit(1);
13740
13836
  }
13741
13837
  console.error(`Connecting to Store Layer API at ${API_URL}...`);
13742
- const manifest = await fetchToolManifest();
13838
+ const [manifest, skillGuide] = await Promise.all([
13839
+ fetchToolManifest(),
13840
+ fetchSkillGuide()
13841
+ ]);
13743
13842
  console.error(`Loaded ${manifest.length} tools from Store Layer`);
13843
+ console.error(`Skill guide loaded from GitHub (${SKILLS_REPO})`);
13744
13844
  const mcpToRegistry = new Map;
13745
13845
  const mcpToManifest = new Map;
13746
13846
  for (const tool of manifest) {
@@ -13748,7 +13848,12 @@ async function main() {
13748
13848
  mcpToRegistry.set(mcpName, tool.name);
13749
13849
  mcpToManifest.set(mcpName, tool);
13750
13850
  }
13751
- const server = new Server({ name: "store-layer", version: "0.1.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: {} } });
13752
13857
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
13753
13858
  tools: manifest.map((tool) => ({
13754
13859
  name: toMcpName(tool.name),
@@ -13799,26 +13904,74 @@ async function main() {
13799
13904
  prompts: [
13800
13905
  {
13801
13906
  name: "store-layer-guide",
13802
- description: "Comprehensive guide for using Store Layer tools — covers all domains (promotions, wallets, users, events, referrals, rules), with examples and best practices."
13803
- }
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
+ }))
13804
13913
  ]
13805
13914
  }));
13806
13915
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
13807
- if (request.params.name !== "store-layer-guide") {
13808
- 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
+ };
13809
13927
  }
13810
- return {
13811
- description: "Store Layer MCP skill guide",
13812
- messages: [
13813
- {
13814
- role: "user",
13815
- content: {
13816
- type: "text",
13817
- 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
13818
13969
  }
13819
- }
13820
- ]
13821
- };
13970
+ ]
13971
+ };
13972
+ } catch (error2) {
13973
+ throw new Error(`Failed to fetch ${file.path}: ${error2 instanceof Error ? error2.message : String(error2)}`);
13974
+ }
13822
13975
  });
13823
13976
  const transport = new StdioServerTransport;
13824
13977
  await server.connect(transport);
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@storelayer/mcp-server",
3
- "version": "0.2.1",
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,274 +0,0 @@
1
- # Store Layer MCP Server — Skill Guide for Claude Desktop
2
-
3
- You have access to the **Store Layer** MCP server, which lets you manage loyalty programs, promotions, wallets, users, events, referrals, and more for a specific project.
4
-
5
- ## How It Works
6
-
7
- All tools are dynamically loaded from the Store Layer API. Tool names use underscores (e.g., `promotions_evaluate_cart`). Tools that require a `userId` parameter operate on a specific external user's data.
8
-
9
- ---
10
-
11
- ## Available Tools by Domain
12
-
13
- ### External Users (`external_users`)
14
- | Tool | Description |
15
- |------|-------------|
16
- | `external_users_get_user` | Get a user by ID |
17
- | `external_users_list_users` | List users with pagination |
18
- | `external_users_lookup_user` | Smart lookup: tries ID match, then email fallback |
19
- | `external_users_register` | Register a new user |
20
- | `external_users_update` | Update a user |
21
- | `external_users_remove` | Remove a user |
22
-
23
- ### Wallet (`wallet`) — all require `userId`
24
- | Tool | Description |
25
- |------|-------------|
26
- | `wallet_get_balance` | Get balance for all asset types |
27
- | `wallet_list_transactions` | List transaction history with filters |
28
- | `wallet_credit` | Add assets (points, tokens, etc.) |
29
- | `wallet_debit` | Remove assets |
30
-
31
- ### Loyalty Events (`events`)
32
- | Tool | Description |
33
- |------|-------------|
34
- | `events_get` | Get a single event by ID |
35
- | `events_list` | List events with filters (type, status, pagination) |
36
- | `events_get_stats` | Aggregate stats: total, processed, pending |
37
-
38
- ### Referral (`referral`)
39
- | Tool | Description |
40
- |------|-------------|
41
- | `referral_get_config` | Get referral program config |
42
- | `referral_list_codes` | List referral codes with filters |
43
- | `referral_get_code_by_referrer` | Look up code by referrer ID |
44
- | `referral_validate_code` | Check if a code is valid |
45
- | `referral_list_referrals` | List referral records |
46
- | `referral_list_events` | List referral audit events |
47
- | `referral_get_stats` | Aggregate stats |
48
- | `referral_get_leaderboard` | Leaderboard ranked by referrals |
49
- | `referral_create_code` | Create a referral code |
50
- | `referral_apply_code` | Apply a code for a referee |
51
- | `referral_deactivate_code` | Deactivate a code |
52
-
53
- ### Project (`project`)
54
- | Tool | Description |
55
- |------|-------------|
56
- | `project_get_config` | Get project config (currency, timezone, settings) |
57
- | `project_list_rules` | List loyalty rules |
58
- | `project_get_rule` | Get a single rule by ID |
59
- | `project_list_integrations` | List integrations |
60
- | `project_list_resources` | List resource definitions |
61
- | `project_add_rule` | Create a loyalty rule |
62
- | `project_update_rule` | Update a rule |
63
- | `project_remove_rule` | Delete a rule |
64
- | `project_test_conditions` | Test conditions against sample data |
65
-
66
- ### Promotions (`promotions`)
67
- | Tool | Description |
68
- |------|-------------|
69
- | `promotions_list` | List promotions with filters |
70
- | `promotions_get_active` | Get all active promotions |
71
- | `promotions_list_coupons` | List coupons |
72
- | `promotions_list_usage` | List usage/redemption records |
73
- | `promotions_get_stats` | Stats for a specific promotion |
74
- | `promotions_get_aggregate_stats` | Aggregate stats across all promotions |
75
- | `promotions_create` | Create a promotion |
76
- | `promotions_create_coupon` | Create a coupon for a promotion |
77
- | `promotions_evaluate_cart` | **Evaluate a cart against active promotions** |
78
-
79
- ### Support (`support`)
80
- | Tool | Description |
81
- |------|-------------|
82
- | `support_get_ticket` | Get a ticket by ID |
83
- | `support_list_tickets` | List tickets with filters |
84
- | `support_get_stats` | Aggregate ticket stats |
85
- | `support_create_ticket` | Create a ticket |
86
- | `support_update_ticket` | Update a ticket |
87
-
88
- ### Surveys (`surveys`)
89
- | Tool | Description |
90
- |------|-------------|
91
- | `surveys_get` | Get a survey by ID |
92
- | `surveys_list` | List surveys |
93
- | `surveys_list_responses` | List responses for a survey |
94
- | `surveys_get_stats` | Survey analytics |
95
- | `surveys_create` | Create a survey |
96
- | `surveys_submit_response` | Submit a response |
97
-
98
- ### Workflows (`workflows`)
99
- | Tool | Description |
100
- |------|-------------|
101
- | `workflows_list` | List workflow executions |
102
- | `workflows_get` | Get a workflow execution with steps |
103
-
104
- ### Stores (`stores`)
105
- | Tool | Description |
106
- |------|-------------|
107
- | `stores_get_store` / `stores_list_stores` | Read stores |
108
- | `stores_create_store` / `stores_update_store` / `stores_remove_store` | Manage stores |
109
- | `stores_get_facility` / `stores_list_facilities` | Read facilities |
110
- | `stores_create_facility` / `stores_update_facility` / `stores_remove_facility` | Manage facilities |
111
-
112
- ### Resources (`resources`)
113
- | Tool | Description |
114
- |------|-------------|
115
- | `resources_list` / `resources_get` | Read resource definitions |
116
- | `resources_add` / `resources_update` / `resources_remove` | Manage resource definitions |
117
-
118
- ---
119
-
120
- ## Domain Knowledge
121
-
122
- ### Promotions Engine
123
-
124
- Every promotion has an `itemsDiscountComputation` script that computes per-item discounts. There are no predefined discount types — the script IS the discount logic.
125
-
126
- **Script environment:**
127
- - `$('cart')` — full cart object. `$('cart').items` is the items array. Each item: `{ id, price, quantity, category, tags, ...custom }`
128
- - `$('cart').total` — cart total
129
- - `$('user')` — current user (if userId provided)
130
- - `$('couponCodes')` — applied coupon codes
131
-
132
- **Return format:** Array of `{ id: string, amount: number }` where `id` = item ID and `amount` = discount to subtract.
133
-
134
- **Condition system:**
135
- - Structure: `{ conditions: [{ leftValue, operator, rightValue, rightType }], combinator: "AND" | "OR" }`
136
- - Fields: `cart.total`, `cart.itemCount`, `cart.uniqueItemCount`, `cart.items[0].price`, etc.
137
- - Operators: `equals`, `notEquals`, `gt`, `gte`, `lt`, `lte`, `contains`, `notContains`, `startsWith`, `endsWith`, `exists`, `notExists`, `regex`, `before`, `after`, `isEmpty`, `isNotEmpty`, `hasKey`
138
-
139
- **Stacking modes:**
140
- - `stackable` — combines with other promotions (default)
141
- - `exclusive` — highest priority wins, all others excluded
142
- - `exclusive_group` — highest priority within its group wins
143
-
144
- **Cart formats:**
145
- - **Standard:** Items have `quantity` field. Engine expands internally.
146
- ```json
147
- { "items": [{ "id": "muffin-001", "price": 3.50, "quantity": 2 }] }
148
- ```
149
- - **Sub-items (pre-expanded):** Each unit is a separate entry with a unique ID and a shared logical ID in another field. Pass `itemIdField` to specify which field to use.
150
- ```json
151
- { "items": [{ "id": "sub-1", "sku": "muffin-001", "price": 3.50 }, { "id": "sub-2", "sku": "muffin-001", "price": 3.50 }] }
152
- ```
153
-
154
- **Coupon system:** Promotions can require a coupon code (`requiresCoupon: true`). Create coupons with `promotions_create_coupon`, validate them during cart evaluation by passing `couponCodes`.
155
-
156
- #### Promotion Examples
157
-
158
- **10% off all items:**
159
- ```json
160
- {
161
- "name": "10% Off Everything",
162
- "status": "active",
163
- "conditions": { "conditions": [], "combinator": "AND" },
164
- "itemsDiscountComputation": {
165
- "script": "const items = $('cart').items;\nreturn items.map(item => ({ id: item.id, amount: item.price * 0.10 }));",
166
- "language": "javascript"
167
- }
168
- }
169
- ```
170
-
171
- **$5 off orders over $50:**
172
- ```json
173
- {
174
- "name": "$5 Off Over $50",
175
- "status": "active",
176
- "conditions": {
177
- "conditions": [{ "leftValue": "cart.total", "operator": "gte", "rightValue": 50, "rightType": "number" }],
178
- "combinator": "AND"
179
- },
180
- "itemsDiscountComputation": {
181
- "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
- "language": "javascript"
183
- }
184
- }
185
- ```
186
-
187
- **BOGO: buy 2+ shoes, cheapest free:**
188
- ```json
189
- {
190
- "itemsDiscountComputation": {
191
- "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
- "language": "javascript"
193
- }
194
- }
195
- ```
196
-
197
- #### Cart Evaluation
198
-
199
- Use `promotions_evaluate_cart` with:
200
- ```json
201
- {
202
- "cart": {
203
- "items": [
204
- { "id": "item-1", "price": 25.00, "quantity": 2, "category": "shoes" },
205
- { "id": "item-2", "price": 15.00, "quantity": 1, "category": "accessories" }
206
- ]
207
- },
208
- "userId": "user_123",
209
- "couponCodes": ["SAVE10"]
210
- }
211
- ```
212
-
213
- Response includes: `summary` (originalTotal, totalDiscount, finalTotal, appliedCount), `items` (per-item discounts), `applied` (matched promotions with discounts), `notApplied` (unmatched promotions with reasons, human-readable messages, and suggestions), `discounts` (flat discount list), `warnings`, and `usageRecords` (when not dry run).
214
-
215
- ---
216
-
217
- ### Loyalty Rules
218
-
219
- Rules trigger when events are ingested. Each rule has conditions (evaluated against runtime context) and actions (executed when conditions match).
220
-
221
- **Condition expressions** use Handlebars syntax: `{{ event.type }}`, `{{ event.payload.amount }}`, `{{ user.email }}`, `{{ history.amount }}`
222
-
223
- **Action types:**
224
- - `reward` — `{ assetType: "points", amount: 10, description: "Purchase reward" }`. Dynamic: `amount: "{{ event.amount }}"`
225
- - `integration` — `{ integrationId: "intg_xxx" }` — triggers webhook/Telegram/Slack
226
- - `apply_referral` / `complete_referral` / `mark_code_used` — referral lifecycle actions
227
-
228
- **Rule example — 1 point per dollar spent:**
229
- ```json
230
- {
231
- "name": "Dollar-to-Points",
232
- "conditions": {
233
- "conditions": [{ "leftValue": "{{ event.type }}", "operator": "equals", "rightValue": "purchase", "rightType": "string" }],
234
- "combinator": "AND"
235
- },
236
- "actions": [{ "type": "reward", "config": { "assetType": "points", "amount": "{{ event.payload.amount }}", "description": "1 point per dollar" } }]
237
- }
238
- ```
239
-
240
- ---
241
-
242
- ### Events & Ingestion
243
-
244
- Events are sent externally via `POST /api/ingest/events` with an API key. The MCP server can read events but not ingest them directly.
245
-
246
- **Processing flow:** Event stored → queued → resources resolved → conditions evaluated → actions executed → event marked processed.
247
-
248
- **Event payload example:**
249
- ```json
250
- { "type": "purchase", "userId": "user123", "payload": { "orderId": "order_abc", "amount": 99.99, "items": [...] } }
251
- ```
252
-
253
- ---
254
-
255
- ### Resources
256
-
257
- Resources provide runtime context for rule evaluation. Types:
258
- - **event** — incoming event data
259
- - **http** — external REST API call
260
- - **database** — PostgreSQL query via integration
261
- - **internal** — Store Layer entity data (users, wallets, transactions via registry tools)
262
- - **payload** — request body data
263
-
264
- Resources are resolved before rule evaluation. Their data is accessible in condition expressions.
265
-
266
- ---
267
-
268
- ## Best Practices
269
-
270
- 1. **Start by exploring** — use `promotions_get_aggregate_stats`, `project_get_config`, or list tools to understand the current state before making changes.
271
- 2. **Test before activating** — create promotions in `draft` status, use `promotions_evaluate_cart` to test with sample carts, then set to `active`.
272
- 3. **Use `project_test_conditions`** to validate rule conditions against sample data before saving.
273
- 4. **For destructive actions** (delete, debit), always confirm with the user first.
274
- 5. **Cart evaluation** returns detailed match info — check `applied` for matched promotions with discounts, `notApplied` for unmatched promotions with reasons/suggestions, and `warnings` for coupon validation issues.