@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.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +654 -0
  3. package/dist/client/http.d.ts +8 -0
  4. package/dist/client/http.js +69 -0
  5. package/dist/config.d.ts +17 -0
  6. package/dist/config.js +40 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +20 -0
  9. package/dist/server.d.ts +4 -0
  10. package/dist/server.js +334 -0
  11. package/dist/tools/achievement.d.ts +983 -0
  12. package/dist/tools/achievement.js +311 -0
  13. package/dist/tools/admin.d.ts +153 -0
  14. package/dist/tools/admin.js +193 -0
  15. package/dist/tools/analytics.d.ts +162 -0
  16. package/dist/tools/analytics.js +245 -0
  17. package/dist/tools/apikey.d.ts +72 -0
  18. package/dist/tools/apikey.js +78 -0
  19. package/dist/tools/audit.d.ts +107 -0
  20. package/dist/tools/audit.js +90 -0
  21. package/dist/tools/badge.d.ts +135 -0
  22. package/dist/tools/badge.js +165 -0
  23. package/dist/tools/campaign.d.ts +1775 -0
  24. package/dist/tools/campaign.js +724 -0
  25. package/dist/tools/export.d.ts +110 -0
  26. package/dist/tools/export.js +147 -0
  27. package/dist/tools/import.d.ts +110 -0
  28. package/dist/tools/import.js +126 -0
  29. package/dist/tools/index.d.ts +22 -0
  30. package/dist/tools/index.js +527 -0
  31. package/dist/tools/member.d.ts +345 -0
  32. package/dist/tools/member.js +358 -0
  33. package/dist/tools/member.test.d.ts +1 -0
  34. package/dist/tools/member.test.js +213 -0
  35. package/dist/tools/points.d.ts +188 -0
  36. package/dist/tools/points.js +306 -0
  37. package/dist/tools/points.test.d.ts +1 -0
  38. package/dist/tools/points.test.js +292 -0
  39. package/dist/tools/reward.d.ts +261 -0
  40. package/dist/tools/reward.js +371 -0
  41. package/dist/tools/reward.test.d.ts +1 -0
  42. package/dist/tools/reward.test.js +240 -0
  43. package/dist/tools/role.d.ts +161 -0
  44. package/dist/tools/role.js +160 -0
  45. package/dist/tools/segment.d.ts +797 -0
  46. package/dist/tools/segment.js +299 -0
  47. package/dist/tools/store.d.ts +101 -0
  48. package/dist/tools/store.js +117 -0
  49. package/dist/tools/tierset.d.ts +288 -0
  50. package/dist/tools/tierset.js +244 -0
  51. package/dist/tools/transaction.d.ts +357 -0
  52. package/dist/tools/transaction.js +242 -0
  53. package/dist/tools/transaction.test.d.ts +1 -0
  54. package/dist/tools/transaction.test.js +235 -0
  55. package/dist/tools/wallet-type.d.ts +32 -0
  56. package/dist/tools/wallet-type.js +58 -0
  57. package/dist/tools/webhook.d.ts +179 -0
  58. package/dist/tools/webhook.js +171 -0
  59. package/dist/types/schemas/achievement.d.ts +1116 -0
  60. package/dist/types/schemas/achievement.js +172 -0
  61. package/dist/types/schemas/admin.d.ts +263 -0
  62. package/dist/types/schemas/admin.js +99 -0
  63. package/dist/types/schemas/analytics.d.ts +542 -0
  64. package/dist/types/schemas/analytics.js +130 -0
  65. package/dist/types/schemas/badge.d.ts +131 -0
  66. package/dist/types/schemas/badge.js +48 -0
  67. package/dist/types/schemas/campaign.d.ts +2005 -0
  68. package/dist/types/schemas/campaign.js +189 -0
  69. package/dist/types/schemas/common.d.ts +52 -0
  70. package/dist/types/schemas/common.js +26 -0
  71. package/dist/types/schemas/export.d.ts +127 -0
  72. package/dist/types/schemas/export.js +43 -0
  73. package/dist/types/schemas/import.d.ts +344 -0
  74. package/dist/types/schemas/import.js +68 -0
  75. package/dist/types/schemas/member.d.ts +443 -0
  76. package/dist/types/schemas/member.js +92 -0
  77. package/dist/types/schemas/points.d.ts +188 -0
  78. package/dist/types/schemas/points.js +54 -0
  79. package/dist/types/schemas/reward.d.ts +278 -0
  80. package/dist/types/schemas/reward.js +69 -0
  81. package/dist/types/schemas/role.d.ts +260 -0
  82. package/dist/types/schemas/role.js +75 -0
  83. package/dist/types/schemas/segment.d.ts +592 -0
  84. package/dist/types/schemas/segment.js +114 -0
  85. package/dist/types/schemas/tierset.d.ts +552 -0
  86. package/dist/types/schemas/tierset.js +87 -0
  87. package/dist/types/schemas/transaction.d.ts +1022 -0
  88. package/dist/types/schemas/transaction.js +63 -0
  89. package/dist/types/schemas/wallet-type.d.ts +99 -0
  90. package/dist/types/schemas/wallet-type.js +17 -0
  91. package/dist/types/schemas/webhook.d.ts +195 -0
  92. package/dist/types/schemas/webhook.js +39 -0
  93. package/dist/utils/cursor.d.ts +84 -0
  94. package/dist/utils/cursor.js +117 -0
  95. package/dist/utils/errors.d.ts +12 -0
  96. package/dist/utils/errors.js +69 -0
  97. package/dist/utils/pagination.d.ts +39 -0
  98. package/dist/utils/pagination.js +77 -0
  99. 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
+ }
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ });
@@ -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 };