@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.
- package/dist/index.js +182 -29
- package/package.json +2 -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
|
|
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
|
|
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
|
|
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
|
-
|
|
13808
|
-
|
|
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
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13815
|
-
|
|
13816
|
-
|
|
13817
|
-
|
|
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.
|
|
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.
|