@open-loyalty/mcp-server 1.0.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/LICENSE +21 -0
- package/README.md +654 -0
- package/dist/client/http.d.ts +8 -0
- package/dist/client/http.js +69 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +40 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +20 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +334 -0
- package/dist/tools/achievement.d.ts +983 -0
- package/dist/tools/achievement.js +311 -0
- package/dist/tools/admin.d.ts +153 -0
- package/dist/tools/admin.js +193 -0
- package/dist/tools/analytics.d.ts +162 -0
- package/dist/tools/analytics.js +245 -0
- package/dist/tools/apikey.d.ts +72 -0
- package/dist/tools/apikey.js +78 -0
- package/dist/tools/audit.d.ts +107 -0
- package/dist/tools/audit.js +90 -0
- package/dist/tools/badge.d.ts +135 -0
- package/dist/tools/badge.js +165 -0
- package/dist/tools/campaign.d.ts +1775 -0
- package/dist/tools/campaign.js +724 -0
- package/dist/tools/export.d.ts +110 -0
- package/dist/tools/export.js +147 -0
- package/dist/tools/import.d.ts +110 -0
- package/dist/tools/import.js +126 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.js +527 -0
- package/dist/tools/member.d.ts +345 -0
- package/dist/tools/member.js +358 -0
- package/dist/tools/member.test.d.ts +1 -0
- package/dist/tools/member.test.js +213 -0
- package/dist/tools/points.d.ts +188 -0
- package/dist/tools/points.js +306 -0
- package/dist/tools/points.test.d.ts +1 -0
- package/dist/tools/points.test.js +292 -0
- package/dist/tools/reward.d.ts +261 -0
- package/dist/tools/reward.js +371 -0
- package/dist/tools/reward.test.d.ts +1 -0
- package/dist/tools/reward.test.js +240 -0
- package/dist/tools/role.d.ts +161 -0
- package/dist/tools/role.js +160 -0
- package/dist/tools/segment.d.ts +797 -0
- package/dist/tools/segment.js +299 -0
- package/dist/tools/store.d.ts +101 -0
- package/dist/tools/store.js +117 -0
- package/dist/tools/tierset.d.ts +288 -0
- package/dist/tools/tierset.js +244 -0
- package/dist/tools/transaction.d.ts +357 -0
- package/dist/tools/transaction.js +242 -0
- package/dist/tools/transaction.test.d.ts +1 -0
- package/dist/tools/transaction.test.js +235 -0
- package/dist/tools/wallet-type.d.ts +32 -0
- package/dist/tools/wallet-type.js +58 -0
- package/dist/tools/webhook.d.ts +179 -0
- package/dist/tools/webhook.js +171 -0
- package/dist/types/schemas/achievement.d.ts +1116 -0
- package/dist/types/schemas/achievement.js +172 -0
- package/dist/types/schemas/admin.d.ts +263 -0
- package/dist/types/schemas/admin.js +99 -0
- package/dist/types/schemas/analytics.d.ts +542 -0
- package/dist/types/schemas/analytics.js +130 -0
- package/dist/types/schemas/badge.d.ts +131 -0
- package/dist/types/schemas/badge.js +48 -0
- package/dist/types/schemas/campaign.d.ts +2005 -0
- package/dist/types/schemas/campaign.js +189 -0
- package/dist/types/schemas/common.d.ts +52 -0
- package/dist/types/schemas/common.js +26 -0
- package/dist/types/schemas/export.d.ts +127 -0
- package/dist/types/schemas/export.js +43 -0
- package/dist/types/schemas/import.d.ts +344 -0
- package/dist/types/schemas/import.js +68 -0
- package/dist/types/schemas/member.d.ts +443 -0
- package/dist/types/schemas/member.js +92 -0
- package/dist/types/schemas/points.d.ts +188 -0
- package/dist/types/schemas/points.js +54 -0
- package/dist/types/schemas/reward.d.ts +278 -0
- package/dist/types/schemas/reward.js +69 -0
- package/dist/types/schemas/role.d.ts +260 -0
- package/dist/types/schemas/role.js +75 -0
- package/dist/types/schemas/segment.d.ts +592 -0
- package/dist/types/schemas/segment.js +114 -0
- package/dist/types/schemas/tierset.d.ts +552 -0
- package/dist/types/schemas/tierset.js +87 -0
- package/dist/types/schemas/transaction.d.ts +1022 -0
- package/dist/types/schemas/transaction.js +63 -0
- package/dist/types/schemas/wallet-type.d.ts +99 -0
- package/dist/types/schemas/wallet-type.js +17 -0
- package/dist/types/schemas/webhook.d.ts +195 -0
- package/dist/types/schemas/webhook.js +39 -0
- package/dist/utils/cursor.d.ts +84 -0
- package/dist/utils/cursor.js +117 -0
- package/dist/utils/errors.d.ts +12 -0
- package/dist/utils/errors.js +69 -0
- package/dist/utils/pagination.d.ts +39 -0
- package/dist/utils/pagination.js +77 -0
- package/package.json +65 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { getConfig } from "../config.js";
|
|
3
|
+
let client = null;
|
|
4
|
+
// For testing: reset the client singleton
|
|
5
|
+
export function resetHttpClient() {
|
|
6
|
+
client = null;
|
|
7
|
+
}
|
|
8
|
+
// For testing: get the underlying axios instance
|
|
9
|
+
export function getAxiosInstance() {
|
|
10
|
+
return getClient();
|
|
11
|
+
}
|
|
12
|
+
function getClient() {
|
|
13
|
+
if (client) {
|
|
14
|
+
return client;
|
|
15
|
+
}
|
|
16
|
+
const config = getConfig();
|
|
17
|
+
client = axios.create({
|
|
18
|
+
baseURL: config.apiUrl,
|
|
19
|
+
timeout: 30000,
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
client.interceptors.request.use((requestConfig) => {
|
|
25
|
+
const cfg = getConfig();
|
|
26
|
+
requestConfig.headers.set("X-AUTH-TOKEN", cfg.apiToken);
|
|
27
|
+
return requestConfig;
|
|
28
|
+
}, (error) => {
|
|
29
|
+
console.error("Request interceptor error:", error.message);
|
|
30
|
+
return Promise.reject(error);
|
|
31
|
+
});
|
|
32
|
+
client.interceptors.response.use((response) => response, (error) => {
|
|
33
|
+
if (error.response) {
|
|
34
|
+
const status = error.response.status;
|
|
35
|
+
const data = error.response.data;
|
|
36
|
+
console.error(`API Error [${status}]:`, JSON.stringify(data, null, 2));
|
|
37
|
+
if (status === 401) {
|
|
38
|
+
throw new Error("Authentication failed (401): Invalid or expired API token. " +
|
|
39
|
+
"Please check your OPENLOYALTY_API_TOKEN environment variable.");
|
|
40
|
+
}
|
|
41
|
+
if (status === 403) {
|
|
42
|
+
throw new Error("Access denied (403): You do not have permission to access this resource. " +
|
|
43
|
+
"Please check your API token permissions.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return Promise.reject(error);
|
|
47
|
+
});
|
|
48
|
+
return client;
|
|
49
|
+
}
|
|
50
|
+
export async function apiGet(url) {
|
|
51
|
+
const response = await getClient().get(url);
|
|
52
|
+
return response.data;
|
|
53
|
+
}
|
|
54
|
+
export async function apiPost(url, data) {
|
|
55
|
+
const response = await getClient().post(url, data);
|
|
56
|
+
return response.data;
|
|
57
|
+
}
|
|
58
|
+
export async function apiPut(url, data) {
|
|
59
|
+
const response = await getClient().put(url, data);
|
|
60
|
+
return response.data;
|
|
61
|
+
}
|
|
62
|
+
export async function apiDelete(url) {
|
|
63
|
+
const response = await getClient().delete(url);
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
export async function apiPatch(url, data) {
|
|
67
|
+
const response = await getClient().patch(url, data);
|
|
68
|
+
return response.data;
|
|
69
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
3
|
+
apiUrl: z.ZodString;
|
|
4
|
+
apiToken: z.ZodString;
|
|
5
|
+
defaultStoreCode: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
apiUrl: string;
|
|
8
|
+
apiToken: string;
|
|
9
|
+
defaultStoreCode: string;
|
|
10
|
+
}, {
|
|
11
|
+
apiUrl: string;
|
|
12
|
+
apiToken: string;
|
|
13
|
+
defaultStoreCode: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
16
|
+
export declare function getConfig(): Config;
|
|
17
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const ConfigSchema = z.object({
|
|
3
|
+
apiUrl: z.string().url(),
|
|
4
|
+
apiToken: z.string().min(1),
|
|
5
|
+
defaultStoreCode: z.string().min(1),
|
|
6
|
+
});
|
|
7
|
+
let config = null;
|
|
8
|
+
export function getConfig() {
|
|
9
|
+
if (config) {
|
|
10
|
+
return config;
|
|
11
|
+
}
|
|
12
|
+
const apiUrl = process.env.OPENLOYALTY_API_URL;
|
|
13
|
+
const apiToken = process.env.OPENLOYALTY_API_TOKEN;
|
|
14
|
+
const defaultStoreCode = process.env.OPENLOYALTY_DEFAULT_STORE_CODE;
|
|
15
|
+
if (!apiUrl) {
|
|
16
|
+
throw new Error("Missing required environment variable: OPENLOYALTY_API_URL. " +
|
|
17
|
+
"Please set this to your Open Loyalty API URL (e.g., https://api.openloyalty.io)");
|
|
18
|
+
}
|
|
19
|
+
if (!apiToken) {
|
|
20
|
+
throw new Error("Missing required environment variable: OPENLOYALTY_API_TOKEN. " +
|
|
21
|
+
"Please set this to your Open Loyalty API authentication token");
|
|
22
|
+
}
|
|
23
|
+
if (!defaultStoreCode) {
|
|
24
|
+
throw new Error("Missing required environment variable: OPENLOYALTY_DEFAULT_STORE_CODE. " +
|
|
25
|
+
"Please set this to your default store code (e.g., default)");
|
|
26
|
+
}
|
|
27
|
+
const result = ConfigSchema.safeParse({
|
|
28
|
+
apiUrl,
|
|
29
|
+
apiToken,
|
|
30
|
+
defaultStoreCode,
|
|
31
|
+
});
|
|
32
|
+
if (!result.success) {
|
|
33
|
+
const errors = result.error.issues
|
|
34
|
+
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
35
|
+
.join(", ");
|
|
36
|
+
throw new Error(`Invalid configuration: ${errors}`);
|
|
37
|
+
}
|
|
38
|
+
config = result.data;
|
|
39
|
+
return config;
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./server.js";
|
|
4
|
+
import { getConfig } from "./config.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
try {
|
|
7
|
+
getConfig();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
console.error("Configuration error:", error instanceof Error ? error.message : error);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const server = createServer();
|
|
14
|
+
const transport = new StdioServerTransport();
|
|
15
|
+
await server.connect(transport);
|
|
16
|
+
}
|
|
17
|
+
main().catch((error) => {
|
|
18
|
+
console.error("Fatal error:", error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
declare const SERVER_INSTRUCTIONS = "\nOpen Loyalty MCP Server - Complete Loyalty Program Management\n\n## Domain Model\n\nMember (loyalty program participant)\n \u2514\u2500\u2500 Points (wallet balance, transfers)\n \u2514\u2500\u2500 Tier (current level: Bronze, Silver, Gold)\n \u2514\u2500\u2500 Transactions (purchase history)\n \u2514\u2500\u2500 Rewards (redeemed coupons)\n \u2514\u2500\u2500 Achievements (gamification progress)\n \u2514\u2500\u2500 Badges (visual rewards from achievements)\n\nTierSet (loyalty program structure)\n \u2514\u2500\u2500 Conditions (criteria: activeUnits, totalSpending)\n \u2514\u2500\u2500 Tiers (levels with thresholds)\n\nWalletType (points currency configuration)\n\nReward (redeemable items)\n \u2514\u2500\u2500 Categories (reward groupings)\n\nCampaign (automated points/rewards rules)\n \u2514\u2500\u2500 Type (earning, spending, custom, instant_reward)\n \u2514\u2500\u2500 Trigger (transaction, custom_event, points_transfer)\n \u2514\u2500\u2500 Rules (SKU, category, transaction amount conditions)\n \u2514\u2500\u2500 Effects (give_points, multiply_points, percentage_discount)\n \u2514\u2500\u2500 Targeting (segments, tiers for audience)\n\nSegment (member audience grouping)\n \u2514\u2500\u2500 Parts (groups of criteria - OR logic between parts)\n \u2514\u2500\u2500 Criteria (conditions - AND logic within part)\n Types: purchase_period, transaction_count, average_transaction,\n anniversary, last_purchase_days, tier\n\nAchievement (gamification goals)\n \u2514\u2500\u2500 Trigger (transaction, custom_event, points_transfer, referral, login)\n \u2514\u2500\u2500 CompleteRule (periodGoal, period type, unique counting)\n \u2514\u2500\u2500 Badge (visual reward when completed)\n\nBadge (visual recognition)\n \u2514\u2500\u2500 Linked to achievements via badgeTypeId\n \u2514\u2500\u2500 Auto-created when referenced by achievement\n\n## Key Workflows\n\n### 1. Create 3-tier Loyalty Program:\nwallet_type_list \u2192 tierset_create \u2192 tierset_get \u2192 tierset_update_tiers\n\n### 2. Register and Reward Member:\nmember_create \u2192 points_add (welcome bonus) \u2192 member_get (verify)\n\n### 3. Record Purchase and Earn Points:\ntransaction_create (with customerData) \u2192 triggers campaigns \u2192 points auto-added\n\n### 4. Redeem Reward:\nreward_list \u2192 reward_buy (deducts points) \u2192 reward_redeem (mark coupon used)\n\n### 5. Assign Unmatched Transaction:\ntransaction_list (matched=false) \u2192 transaction_assign_member \u2192 points earned\n\n### 6. Check Member Tier Progress:\nmember_get_tier_progress \u2192 shows current tier, next tier, progress %\n\n### 7. Double Points for VIP Members:\nsegment_create (with tier criteria for VIP level) \u2192 campaign_create (targeting that segment with give_points effect and multiplier: 2)\n\n### 8. Purchase Achievement with Badge:\nbadge_list (find or note badgeTypeId) \u2192 achievement_create (trigger: transaction, periodGoal for amount, badgeTypeId)\n\n### 9. Create Targeted Promotion:\nsegment_create (define audience) \u2192 campaign_create (type: earning, target: segment, segmentIds: [segmentId])\n\n### 10. Track Member Gamification:\nachievement_list_member_achievements \u2192 badge_get_member_badges \u2192 achievement_get_member_progress\n\n## Discovery Paths:\n- Members: member_list \u2192 member_get \u2192 points_get_balance\n- Tiers: tierset_list \u2192 tierset_get \u2192 tierset_get_tiers\n- Rewards: reward_category_list \u2192 reward_list \u2192 reward_get\n- Transactions: transaction_list \u2192 transaction_get\n- Campaigns: campaign_list \u2192 campaign_get \u2192 campaign_simulate\n- Segments: segment_list \u2192 segment_get \u2192 segment_get_members \u2192 campaign_create (audience targeting)\n- Achievements: achievement_list \u2192 badge_list \u2192 achievement_create (with badge)\n- Member Gamification: achievement_list_member_achievements \u2192 badge_get_member_badges\n\n## Important Patterns:\n- conditionId REQUIRED for tier thresholds - call tierset_get after tierset_create\n- Points operations: check balance with points_get_balance before spending\n- Reward flow: reward_buy returns couponCode, use reward_redeem to mark used\n- Transactions auto-match to members via customerData (email, phone, loyaltyCardNumber)\n\n### Pagination:\n- Traditional pagination: use page + perPage params\n- Cursor pagination: provide cursor from previous response for efficient deep pagination\n- Cursor-enabled tools: member_list, transaction_list, points_get_history\n- First scroll request: cursor=\"\" (empty string)\n- Subsequent requests: use cursor value from previous response\n- When cursor provided, page param is ignored\n\n### Campaign Patterns:\n- Campaign types: earning (earn points), spending (spend points), custom (custom events), instant_reward\n- Triggers: transaction (purchase), custom_event, points_transfer, referral, registration\n- Effects: give_points, multiply_points, percentage_discount, fixed_discount\n- Target campaigns to segments or tiers using target: \"segment\" and segmentIds array\n\n### Segment Patterns:\n- Parts use OR logic (member matches ANY part)\n- Criteria within a part use AND logic (member must match ALL criteria in that part)\n- Common criteria: purchase_period (spending), transaction_count, average_transaction, anniversary, last_purchase_days, tier\n\n### Achievement Patterns:\n- Triggers: transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, registration, profile_update, login\n- periodGoal sets the target (e.g., 5 purchases, 1000 points spent)\n- period defines timeframe: day, week, month, year, forever\n- Link to badge via badgeTypeId - badge auto-created if doesn't exist\n- uniqueAttribute for counting distinct values (e.g., unique products)\n\n## Phase 3: Analytics, Admin, Roles & Stores\n\n### Domain Model (Extended)\n\nAdmin (system user)\n \u2514\u2500\u2500 Roles (permission sets)\n \u2514\u2500\u2500 Permissions (resource + access level)\n \u2514\u2500\u2500 API Keys (programmatic access)\n\nStore (multi-tenant container)\n \u2514\u2500\u2500 Members, Campaigns, Rewards (isolated per store)\n\nAudit Log (compliance tracking)\n \u2514\u2500\u2500 eventType, entityType, entityId, username, timestamp\n\n### Analytics Workflows:\n\n#### 11. Get Program Overview:\nanalytics_dashboard \u2192 analytics_tiers \u2192 analytics_members\n\n#### 12. Analyze Points Economy:\nanalytics_points \u2192 analytics_units (per wallet) \u2192 points_get_histogram\n\n#### 13. Measure Campaign Performance:\nanalytics_campaigns \u2192 analytics_campaign_detail (specific campaign)\n\n#### 14. Track Transactions:\nanalytics_transactions \u2192 transaction_list (details)\n\n### Aggregation Queries (Top Spenders, Purchase Analysis):\n\nIMPORTANT: For queries like \"top 5 spenders in July 2025\", use this approach:\n\n#### 15. Find Top Spenders by Date Range:\n1. transaction_list(purchasedAtFrom, purchasedAtTo, perPage=50, cursor='') - returns customerId with each transaction\n2. Use cursor pagination to fetch ALL pages - even if there are 1000+ transactions\n3. Aggregate grossValue by customerId in your code\n4. Sort by total spent, take top N\n5. member_get for each top spender to get names/details\n\nCRITICAL - DO NOT TRY TO BE SMART OR OPTIMIZE:\n- ALWAYS iterate through ALL pages using cursor pagination - this is the ONLY correct approach\n- DO NOT skip pages or try to sample data - you will get inaccurate results\n- DO NOT use transaction_get individually - transaction_list already includes customerId\n- DO NOT try to find \"smarter\" analytics endpoints - they don't exist for per-customer aggregation\n- Large datasets (1000+ transactions) are normal - just keep paginating until cursor is empty\n- Start with cursor='' (empty string), use returned cursor for next request, repeat until done\n\nExample: Top 5 spenders July 2025\n- First request: transaction_list(purchasedAtFrom: \"2025-07-01\", purchasedAtTo: \"2025-07-31\", perPage: 50, cursor: \"\")\n- Each result includes: transactionId, grossValue, customerId, purchasedAt\n- Keep fetching with returned cursor until response has no cursor or empty transactions\n- Even if there are 1500 transactions (30 pages), iterate through ALL of them\n- Group by customerId, sum grossValue, sort descending, take 5\n\n### Admin & Security Workflows:\n\n#### 15. Create Admin with Limited Role:\nacl_get_resources \u2192 role_create \u2192 admin_create (with roleId)\n\n#### 16. Generate API Key for Integration:\nadmin_get \u2192 apikey_create \u2192 SAVE TOKEN IMMEDIATELY (shown once!)\n\n#### 17. Audit User Actions:\naudit_list (filter by username/entity) \u2192 audit_export (compliance report)\n\n### Store Multi-Tenancy:\n\n#### 18. Create New Store:\nstore_create \u2192 configure campaigns/tiers \u2192 member_create (with storeCode)\n\n### Analytics Query Patterns:\n- All analytics tools support dateFrom/dateTo ISO date filters\n- analytics_dashboard: high-level program metrics\n- analytics_units: wallet-specific metrics (requires walletTypeCode)\n- analytics_campaign_detail: detailed metrics for single campaign\n\n### Admin \u2192 Role \u2192 Permission Model:\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Admin \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Role \u2502\u2500\u2500\u2500\u2500\u25B6\u2502 Permission \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502 \u2502\n \u2502 resource + access\n \u2502 (VIEW, MODIFY, etc.)\n \u25BC\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 API Key \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n### Store Multi-Tenancy:\n- Each store is isolated: members, campaigns, rewards, transactions\n- storeCode parameter routes requests to correct tenant\n- Default store used when storeCode omitted\n\n## Phase 4: Webhooks, Import & Export\n\n### Domain Model (Extended)\n\nWebhook Subscription (event notification)\n \u2514\u2500\u2500 eventName (event to subscribe to)\n \u2514\u2500\u2500 url (callback endpoint)\n \u2514\u2500\u2500 headers (custom HTTP headers)\n\nImport (bulk data upload)\n \u2514\u2500\u2500 type: member, groupValue, segmentMembers, unitTransferAdding, etc.\n \u2514\u2500\u2500 status: pending, processing, succeed, failed\n \u2514\u2500\u2500 items (individual row results)\n\nExport (bulk data download)\n \u2514\u2500\u2500 type: campaignCode, member, memberTier, memberSegment, rewardFulfillment\n \u2514\u2500\u2500 status: pending, done, failed, error\n \u2514\u2500\u2500 CSV file (when status='done')\n\n### Webhook Workflows:\n\n#### 19. Subscribe to Member Events for CRM Sync:\nwebhook_events \u2192 webhook_create (eventName: 'member.created', url: 'https://crm.example.com/webhook')\n\n#### 20. List and Manage Subscriptions:\nwebhook_list \u2192 webhook_get \u2192 webhook_update or webhook_delete\n\n### Import Workflows:\n\n#### 21. Bulk Import Members from CSV:\nimport_create (type: 'member', fileContent: CSV data) \u2192 import_list \u2192 import_get (check status)\n\n#### 22. Bulk Add Points to Members:\nimport_create (type: 'unitTransferAdding', fileContent: CSV) \u2192 poll import_get until complete\n\n### Export Workflows:\n\n#### 23. Export Campaign Codes:\nexport_create (type: 'campaignCode', filters: { campaignId }) \u2192 poll export_get (until status='done') \u2192 export_download\n\n#### 24. Export Member Data:\nexport_create (type: 'member') \u2192 export_get \u2192 export_download (returns CSV)\n\n### Webhook Patterns:\n- Use webhook_events to discover available event types before subscribing\n- API uses wrapper: { webhookSubscription: { eventName, url, headers? } }\n- Common events: member.created, member.updated, transaction.created, reward.purchased\n\n### Import Patterns:\n- Import is async: create returns importId, poll status with import_get\n- CSV format required - provide plain text, not base64\n- Types: member, groupValue, segmentMembers, unitTransferAdding, unitTransferSpending, transaction, campaignCode, rewardCoupon\n\n### Export Patterns:\n- Export is async: create returns exportId, poll status until 'done'\n- API body wrapper varies by type: { campaignCode: { filters... } }\n- Only call export_download when status='done'\n- Types: campaignCode, member, memberTier, memberSegment, rewardFulfillment\n";
|
|
3
|
+
export declare function createServer(): McpServer;
|
|
4
|
+
export { SERVER_INSTRUCTIONS };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { getAllTools, getToolHandler, toolMetadata } from "./tools/index.js";
|
|
3
|
+
import { OpenLoyaltyError } from "./utils/errors.js";
|
|
4
|
+
const SERVER_INSTRUCTIONS = `
|
|
5
|
+
Open Loyalty MCP Server - Complete Loyalty Program Management
|
|
6
|
+
|
|
7
|
+
## Domain Model
|
|
8
|
+
|
|
9
|
+
Member (loyalty program participant)
|
|
10
|
+
└── Points (wallet balance, transfers)
|
|
11
|
+
└── Tier (current level: Bronze, Silver, Gold)
|
|
12
|
+
└── Transactions (purchase history)
|
|
13
|
+
└── Rewards (redeemed coupons)
|
|
14
|
+
└── Achievements (gamification progress)
|
|
15
|
+
└── Badges (visual rewards from achievements)
|
|
16
|
+
|
|
17
|
+
TierSet (loyalty program structure)
|
|
18
|
+
└── Conditions (criteria: activeUnits, totalSpending)
|
|
19
|
+
└── Tiers (levels with thresholds)
|
|
20
|
+
|
|
21
|
+
WalletType (points currency configuration)
|
|
22
|
+
|
|
23
|
+
Reward (redeemable items)
|
|
24
|
+
└── Categories (reward groupings)
|
|
25
|
+
|
|
26
|
+
Campaign (automated points/rewards rules)
|
|
27
|
+
└── Type (earning, spending, custom, instant_reward)
|
|
28
|
+
└── Trigger (transaction, custom_event, points_transfer)
|
|
29
|
+
└── Rules (SKU, category, transaction amount conditions)
|
|
30
|
+
└── Effects (give_points, multiply_points, percentage_discount)
|
|
31
|
+
└── Targeting (segments, tiers for audience)
|
|
32
|
+
|
|
33
|
+
Segment (member audience grouping)
|
|
34
|
+
└── Parts (groups of criteria - OR logic between parts)
|
|
35
|
+
└── Criteria (conditions - AND logic within part)
|
|
36
|
+
Types: purchase_period, transaction_count, average_transaction,
|
|
37
|
+
anniversary, last_purchase_days, tier
|
|
38
|
+
|
|
39
|
+
Achievement (gamification goals)
|
|
40
|
+
└── Trigger (transaction, custom_event, points_transfer, referral, login)
|
|
41
|
+
└── CompleteRule (periodGoal, period type, unique counting)
|
|
42
|
+
└── Badge (visual reward when completed)
|
|
43
|
+
|
|
44
|
+
Badge (visual recognition)
|
|
45
|
+
└── Linked to achievements via badgeTypeId
|
|
46
|
+
└── Auto-created when referenced by achievement
|
|
47
|
+
|
|
48
|
+
## Key Workflows
|
|
49
|
+
|
|
50
|
+
### 1. Create 3-tier Loyalty Program:
|
|
51
|
+
wallet_type_list → tierset_create → tierset_get → tierset_update_tiers
|
|
52
|
+
|
|
53
|
+
### 2. Register and Reward Member:
|
|
54
|
+
member_create → points_add (welcome bonus) → member_get (verify)
|
|
55
|
+
|
|
56
|
+
### 3. Record Purchase and Earn Points:
|
|
57
|
+
transaction_create (with customerData) → triggers campaigns → points auto-added
|
|
58
|
+
|
|
59
|
+
### 4. Redeem Reward:
|
|
60
|
+
reward_list → reward_buy (deducts points) → reward_redeem (mark coupon used)
|
|
61
|
+
|
|
62
|
+
### 5. Assign Unmatched Transaction:
|
|
63
|
+
transaction_list (matched=false) → transaction_assign_member → points earned
|
|
64
|
+
|
|
65
|
+
### 6. Check Member Tier Progress:
|
|
66
|
+
member_get_tier_progress → shows current tier, next tier, progress %
|
|
67
|
+
|
|
68
|
+
### 7. Double Points for VIP Members:
|
|
69
|
+
segment_create (with tier criteria for VIP level) → campaign_create (targeting that segment with give_points effect and multiplier: 2)
|
|
70
|
+
|
|
71
|
+
### 8. Purchase Achievement with Badge:
|
|
72
|
+
badge_list (find or note badgeTypeId) → achievement_create (trigger: transaction, periodGoal for amount, badgeTypeId)
|
|
73
|
+
|
|
74
|
+
### 9. Create Targeted Promotion:
|
|
75
|
+
segment_create (define audience) → campaign_create (type: earning, target: segment, segmentIds: [segmentId])
|
|
76
|
+
|
|
77
|
+
### 10. Track Member Gamification:
|
|
78
|
+
achievement_list_member_achievements → badge_get_member_badges → achievement_get_member_progress
|
|
79
|
+
|
|
80
|
+
## Discovery Paths:
|
|
81
|
+
- Members: member_list → member_get → points_get_balance
|
|
82
|
+
- Tiers: tierset_list → tierset_get → tierset_get_tiers
|
|
83
|
+
- Rewards: reward_category_list → reward_list → reward_get
|
|
84
|
+
- Transactions: transaction_list → transaction_get
|
|
85
|
+
- Campaigns: campaign_list → campaign_get → campaign_simulate
|
|
86
|
+
- Segments: segment_list → segment_get → segment_get_members → campaign_create (audience targeting)
|
|
87
|
+
- Achievements: achievement_list → badge_list → achievement_create (with badge)
|
|
88
|
+
- Member Gamification: achievement_list_member_achievements → badge_get_member_badges
|
|
89
|
+
|
|
90
|
+
## Important Patterns:
|
|
91
|
+
- conditionId REQUIRED for tier thresholds - call tierset_get after tierset_create
|
|
92
|
+
- Points operations: check balance with points_get_balance before spending
|
|
93
|
+
- Reward flow: reward_buy returns couponCode, use reward_redeem to mark used
|
|
94
|
+
- Transactions auto-match to members via customerData (email, phone, loyaltyCardNumber)
|
|
95
|
+
|
|
96
|
+
### Pagination:
|
|
97
|
+
- Traditional pagination: use page + perPage params
|
|
98
|
+
- Cursor pagination: provide cursor from previous response for efficient deep pagination
|
|
99
|
+
- Cursor-enabled tools: member_list, transaction_list, points_get_history
|
|
100
|
+
- First scroll request: cursor="" (empty string)
|
|
101
|
+
- Subsequent requests: use cursor value from previous response
|
|
102
|
+
- When cursor provided, page param is ignored
|
|
103
|
+
|
|
104
|
+
### Campaign Patterns:
|
|
105
|
+
- Campaign types: earning (earn points), spending (spend points), custom (custom events), instant_reward
|
|
106
|
+
- Triggers: transaction (purchase), custom_event, points_transfer, referral, registration
|
|
107
|
+
- Effects: give_points, multiply_points, percentage_discount, fixed_discount
|
|
108
|
+
- Target campaigns to segments or tiers using target: "segment" and segmentIds array
|
|
109
|
+
|
|
110
|
+
### Segment Patterns:
|
|
111
|
+
- Parts use OR logic (member matches ANY part)
|
|
112
|
+
- Criteria within a part use AND logic (member must match ALL criteria in that part)
|
|
113
|
+
- Common criteria: purchase_period (spending), transaction_count, average_transaction, anniversary, last_purchase_days, tier
|
|
114
|
+
|
|
115
|
+
### Achievement Patterns:
|
|
116
|
+
- Triggers: transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, registration, profile_update, login
|
|
117
|
+
- periodGoal sets the target (e.g., 5 purchases, 1000 points spent)
|
|
118
|
+
- period defines timeframe: day, week, month, year, forever
|
|
119
|
+
- Link to badge via badgeTypeId - badge auto-created if doesn't exist
|
|
120
|
+
- uniqueAttribute for counting distinct values (e.g., unique products)
|
|
121
|
+
|
|
122
|
+
## Phase 3: Analytics, Admin, Roles & Stores
|
|
123
|
+
|
|
124
|
+
### Domain Model (Extended)
|
|
125
|
+
|
|
126
|
+
Admin (system user)
|
|
127
|
+
└── Roles (permission sets)
|
|
128
|
+
└── Permissions (resource + access level)
|
|
129
|
+
└── API Keys (programmatic access)
|
|
130
|
+
|
|
131
|
+
Store (multi-tenant container)
|
|
132
|
+
└── Members, Campaigns, Rewards (isolated per store)
|
|
133
|
+
|
|
134
|
+
Audit Log (compliance tracking)
|
|
135
|
+
└── eventType, entityType, entityId, username, timestamp
|
|
136
|
+
|
|
137
|
+
### Analytics Workflows:
|
|
138
|
+
|
|
139
|
+
#### 11. Get Program Overview:
|
|
140
|
+
analytics_dashboard → analytics_tiers → analytics_members
|
|
141
|
+
|
|
142
|
+
#### 12. Analyze Points Economy:
|
|
143
|
+
analytics_points → analytics_units (per wallet) → points_get_histogram
|
|
144
|
+
|
|
145
|
+
#### 13. Measure Campaign Performance:
|
|
146
|
+
analytics_campaigns → analytics_campaign_detail (specific campaign)
|
|
147
|
+
|
|
148
|
+
#### 14. Track Transactions:
|
|
149
|
+
analytics_transactions → transaction_list (details)
|
|
150
|
+
|
|
151
|
+
### Aggregation Queries (Top Spenders, Purchase Analysis):
|
|
152
|
+
|
|
153
|
+
IMPORTANT: For queries like "top 5 spenders in July 2025", use this approach:
|
|
154
|
+
|
|
155
|
+
#### 15. Find Top Spenders by Date Range:
|
|
156
|
+
1. transaction_list(purchasedAtFrom, purchasedAtTo, perPage=50, cursor='') - returns customerId with each transaction
|
|
157
|
+
2. Use cursor pagination to fetch ALL pages - even if there are 1000+ transactions
|
|
158
|
+
3. Aggregate grossValue by customerId in your code
|
|
159
|
+
4. Sort by total spent, take top N
|
|
160
|
+
5. member_get for each top spender to get names/details
|
|
161
|
+
|
|
162
|
+
CRITICAL - DO NOT TRY TO BE SMART OR OPTIMIZE:
|
|
163
|
+
- ALWAYS iterate through ALL pages using cursor pagination - this is the ONLY correct approach
|
|
164
|
+
- DO NOT skip pages or try to sample data - you will get inaccurate results
|
|
165
|
+
- DO NOT use transaction_get individually - transaction_list already includes customerId
|
|
166
|
+
- DO NOT try to find "smarter" analytics endpoints - they don't exist for per-customer aggregation
|
|
167
|
+
- Large datasets (1000+ transactions) are normal - just keep paginating until cursor is empty
|
|
168
|
+
- Start with cursor='' (empty string), use returned cursor for next request, repeat until done
|
|
169
|
+
|
|
170
|
+
Example: Top 5 spenders July 2025
|
|
171
|
+
- First request: transaction_list(purchasedAtFrom: "2025-07-01", purchasedAtTo: "2025-07-31", perPage: 50, cursor: "")
|
|
172
|
+
- Each result includes: transactionId, grossValue, customerId, purchasedAt
|
|
173
|
+
- Keep fetching with returned cursor until response has no cursor or empty transactions
|
|
174
|
+
- Even if there are 1500 transactions (30 pages), iterate through ALL of them
|
|
175
|
+
- Group by customerId, sum grossValue, sort descending, take 5
|
|
176
|
+
|
|
177
|
+
### Admin & Security Workflows:
|
|
178
|
+
|
|
179
|
+
#### 15. Create Admin with Limited Role:
|
|
180
|
+
acl_get_resources → role_create → admin_create (with roleId)
|
|
181
|
+
|
|
182
|
+
#### 16. Generate API Key for Integration:
|
|
183
|
+
admin_get → apikey_create → SAVE TOKEN IMMEDIATELY (shown once!)
|
|
184
|
+
|
|
185
|
+
#### 17. Audit User Actions:
|
|
186
|
+
audit_list (filter by username/entity) → audit_export (compliance report)
|
|
187
|
+
|
|
188
|
+
### Store Multi-Tenancy:
|
|
189
|
+
|
|
190
|
+
#### 18. Create New Store:
|
|
191
|
+
store_create → configure campaigns/tiers → member_create (with storeCode)
|
|
192
|
+
|
|
193
|
+
### Analytics Query Patterns:
|
|
194
|
+
- All analytics tools support dateFrom/dateTo ISO date filters
|
|
195
|
+
- analytics_dashboard: high-level program metrics
|
|
196
|
+
- analytics_units: wallet-specific metrics (requires walletTypeCode)
|
|
197
|
+
- analytics_campaign_detail: detailed metrics for single campaign
|
|
198
|
+
|
|
199
|
+
### Admin → Role → Permission Model:
|
|
200
|
+
┌─────────┐ ┌──────┐ ┌────────────┐
|
|
201
|
+
│ Admin │────▶│ Role │────▶│ Permission │
|
|
202
|
+
└─────────┘ └──────┘ └────────────┘
|
|
203
|
+
│ │
|
|
204
|
+
│ resource + access
|
|
205
|
+
│ (VIEW, MODIFY, etc.)
|
|
206
|
+
▼
|
|
207
|
+
┌─────────┐
|
|
208
|
+
│ API Key │
|
|
209
|
+
└─────────┘
|
|
210
|
+
|
|
211
|
+
### Store Multi-Tenancy:
|
|
212
|
+
- Each store is isolated: members, campaigns, rewards, transactions
|
|
213
|
+
- storeCode parameter routes requests to correct tenant
|
|
214
|
+
- Default store used when storeCode omitted
|
|
215
|
+
|
|
216
|
+
## Phase 4: Webhooks, Import & Export
|
|
217
|
+
|
|
218
|
+
### Domain Model (Extended)
|
|
219
|
+
|
|
220
|
+
Webhook Subscription (event notification)
|
|
221
|
+
└── eventName (event to subscribe to)
|
|
222
|
+
└── url (callback endpoint)
|
|
223
|
+
└── headers (custom HTTP headers)
|
|
224
|
+
|
|
225
|
+
Import (bulk data upload)
|
|
226
|
+
└── type: member, groupValue, segmentMembers, unitTransferAdding, etc.
|
|
227
|
+
└── status: pending, processing, succeed, failed
|
|
228
|
+
└── items (individual row results)
|
|
229
|
+
|
|
230
|
+
Export (bulk data download)
|
|
231
|
+
└── type: campaignCode, member, memberTier, memberSegment, rewardFulfillment
|
|
232
|
+
└── status: pending, done, failed, error
|
|
233
|
+
└── CSV file (when status='done')
|
|
234
|
+
|
|
235
|
+
### Webhook Workflows:
|
|
236
|
+
|
|
237
|
+
#### 19. Subscribe to Member Events for CRM Sync:
|
|
238
|
+
webhook_events → webhook_create (eventName: 'member.created', url: 'https://crm.example.com/webhook')
|
|
239
|
+
|
|
240
|
+
#### 20. List and Manage Subscriptions:
|
|
241
|
+
webhook_list → webhook_get → webhook_update or webhook_delete
|
|
242
|
+
|
|
243
|
+
### Import Workflows:
|
|
244
|
+
|
|
245
|
+
#### 21. Bulk Import Members from CSV:
|
|
246
|
+
import_create (type: 'member', fileContent: CSV data) → import_list → import_get (check status)
|
|
247
|
+
|
|
248
|
+
#### 22. Bulk Add Points to Members:
|
|
249
|
+
import_create (type: 'unitTransferAdding', fileContent: CSV) → poll import_get until complete
|
|
250
|
+
|
|
251
|
+
### Export Workflows:
|
|
252
|
+
|
|
253
|
+
#### 23. Export Campaign Codes:
|
|
254
|
+
export_create (type: 'campaignCode', filters: { campaignId }) → poll export_get (until status='done') → export_download
|
|
255
|
+
|
|
256
|
+
#### 24. Export Member Data:
|
|
257
|
+
export_create (type: 'member') → export_get → export_download (returns CSV)
|
|
258
|
+
|
|
259
|
+
### Webhook Patterns:
|
|
260
|
+
- Use webhook_events to discover available event types before subscribing
|
|
261
|
+
- API uses wrapper: { webhookSubscription: { eventName, url, headers? } }
|
|
262
|
+
- Common events: member.created, member.updated, transaction.created, reward.purchased
|
|
263
|
+
|
|
264
|
+
### Import Patterns:
|
|
265
|
+
- Import is async: create returns importId, poll status with import_get
|
|
266
|
+
- CSV format required - provide plain text, not base64
|
|
267
|
+
- Types: member, groupValue, segmentMembers, unitTransferAdding, unitTransferSpending, transaction, campaignCode, rewardCoupon
|
|
268
|
+
|
|
269
|
+
### Export Patterns:
|
|
270
|
+
- Export is async: create returns exportId, poll status until 'done'
|
|
271
|
+
- API body wrapper varies by type: { campaignCode: { filters... } }
|
|
272
|
+
- Only call export_download when status='done'
|
|
273
|
+
- Types: campaignCode, member, memberTier, memberSegment, rewardFulfillment
|
|
274
|
+
`;
|
|
275
|
+
export function createServer() {
|
|
276
|
+
const server = new McpServer({
|
|
277
|
+
name: "openloyalty",
|
|
278
|
+
version: "1.0.0",
|
|
279
|
+
});
|
|
280
|
+
const tools = getAllTools();
|
|
281
|
+
for (const tool of tools) {
|
|
282
|
+
const metadata = toolMetadata[tool.name];
|
|
283
|
+
server.registerTool(tool.name, {
|
|
284
|
+
title: metadata?.title,
|
|
285
|
+
description: tool.description,
|
|
286
|
+
inputSchema: tool.inputSchema,
|
|
287
|
+
annotations: metadata?.annotations,
|
|
288
|
+
}, async (args) => {
|
|
289
|
+
const handler = getToolHandler(tool.name);
|
|
290
|
+
if (!handler) {
|
|
291
|
+
return {
|
|
292
|
+
content: [
|
|
293
|
+
{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: `Tool handler not found: ${tool.name}`,
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
isError: true,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
const result = await handler(args);
|
|
303
|
+
return {
|
|
304
|
+
content: [
|
|
305
|
+
{
|
|
306
|
+
type: "text",
|
|
307
|
+
text: result === undefined ? "Success" : JSON.stringify(result, null, 2),
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
let errorText;
|
|
314
|
+
if (error instanceof OpenLoyaltyError) {
|
|
315
|
+
errorText = `Error: ${error.message}\nCode: ${error.code}\nHint: ${error.hint}`;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
errorText = `Error: ${error instanceof Error ? error.message : String(error)}`;
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
content: [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: errorText,
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
isError: true,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return server;
|
|
333
|
+
}
|
|
334
|
+
export { SERVER_INSTRUCTIONS };
|