@open-loyalty/mcp-server 1.5.3 → 1.7.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 (132) hide show
  1. package/dist/config.d.ts +4 -0
  2. package/dist/config.js +11 -0
  3. package/dist/index.js +0 -8
  4. package/dist/server.js +13 -0
  5. package/dist/tools/achievement/handlers.js +47 -0
  6. package/dist/tools/achievement/index.d.ts +11 -4
  7. package/dist/tools/achievement/index.js +12 -1
  8. package/dist/tools/achievement/schemas.d.ts +4 -4
  9. package/dist/tools/achievement/schemas.js +13 -12
  10. package/dist/tools/admin/handlers.d.ts +48 -0
  11. package/dist/tools/admin/handlers.js +159 -0
  12. package/dist/tools/admin/index.d.ts +86 -0
  13. package/dist/tools/admin/index.js +64 -0
  14. package/dist/tools/admin/schemas.d.ts +40 -0
  15. package/dist/tools/admin/schemas.js +40 -0
  16. package/dist/tools/analytics/handlers.d.ts +42 -0
  17. package/dist/tools/analytics/handlers.js +282 -0
  18. package/dist/tools/analytics/index.d.ts +108 -0
  19. package/dist/tools/analytics/index.js +91 -0
  20. package/dist/tools/analytics/schemas.d.ts +42 -0
  21. package/dist/tools/analytics/schemas.js +47 -0
  22. package/dist/tools/apikey/handlers.d.ts +15 -0
  23. package/dist/tools/apikey/handlers.js +53 -0
  24. package/dist/tools/apikey/index.d.ts +41 -0
  25. package/dist/tools/apikey/index.js +38 -0
  26. package/dist/tools/apikey/schemas.d.ts +31 -0
  27. package/dist/tools/apikey/schemas.js +15 -0
  28. package/dist/tools/audit/handlers.d.ts +20 -0
  29. package/dist/tools/audit/handlers.js +82 -0
  30. package/dist/tools/audit/index.d.ts +36 -0
  31. package/dist/tools/audit/index.js +28 -0
  32. package/dist/tools/audit/schemas.d.ts +62 -0
  33. package/dist/tools/audit/schemas.js +18 -0
  34. package/dist/tools/badge/handlers.d.ts +45 -0
  35. package/dist/tools/badge/handlers.js +135 -0
  36. package/dist/tools/badge/index.d.ts +68 -0
  37. package/dist/tools/badge/index.js +47 -0
  38. package/dist/tools/badge/schemas.d.ts +37 -0
  39. package/dist/tools/badge/schemas.js +31 -0
  40. package/dist/tools/campaign/handlers.js +61 -0
  41. package/dist/tools/campaign/index.d.ts +12 -0
  42. package/dist/tools/campaign/index.js +20 -1
  43. package/dist/tools/campaign/member-handlers.js +37 -1
  44. package/dist/tools/campaign/schemas.js +16 -14
  45. package/dist/tools/custom-event/handlers.d.ts +98 -0
  46. package/dist/tools/custom-event/handlers.js +238 -0
  47. package/dist/tools/custom-event/index.d.ts +139 -0
  48. package/dist/tools/custom-event/index.js +78 -0
  49. package/dist/tools/custom-event/schemas.d.ts +87 -0
  50. package/dist/tools/custom-event/schemas.js +59 -0
  51. package/dist/tools/export/handlers.d.ts +29 -0
  52. package/dist/tools/export/handlers.js +128 -0
  53. package/dist/tools/export/index.d.ts +56 -0
  54. package/dist/tools/export/index.js +46 -0
  55. package/dist/tools/export/schemas.d.ts +42 -0
  56. package/dist/tools/export/schemas.js +41 -0
  57. package/dist/tools/import/handlers.d.ts +22 -0
  58. package/dist/tools/import/handlers.js +123 -0
  59. package/dist/tools/import/index.d.ts +45 -0
  60. package/dist/tools/import/index.js +41 -0
  61. package/dist/tools/import/schemas.d.ts +57 -0
  62. package/dist/tools/import/schemas.js +39 -0
  63. package/dist/tools/index.d.ts +1 -0
  64. package/dist/tools/index.js +11 -11
  65. package/dist/tools/member/handlers.js +30 -0
  66. package/dist/tools/member/index.d.ts +10 -0
  67. package/dist/tools/member/index.js +10 -0
  68. package/dist/tools/member/schemas.js +13 -13
  69. package/dist/tools/points/handlers.js +73 -0
  70. package/dist/tools/points/index.d.ts +6 -0
  71. package/dist/tools/points/index.js +6 -0
  72. package/dist/tools/points/schemas.js +1 -1
  73. package/dist/tools/referral/index.d.ts +3 -0
  74. package/dist/tools/referral/index.js +3 -0
  75. package/dist/tools/reward/handlers.js +21 -13
  76. package/dist/tools/reward/index.d.ts +9 -0
  77. package/dist/tools/reward/index.js +12 -1
  78. package/dist/tools/reward/schemas.js +2 -2
  79. package/dist/tools/role/handlers.d.ts +35 -0
  80. package/dist/tools/role/handlers.js +127 -0
  81. package/dist/tools/role/index.d.ts +99 -0
  82. package/dist/tools/role/index.js +65 -0
  83. package/dist/tools/role/schemas.d.ts +56 -0
  84. package/dist/tools/role/schemas.js +35 -0
  85. package/dist/tools/segment/handlers.js +68 -1
  86. package/dist/tools/segment/index.d.ts +9 -0
  87. package/dist/tools/segment/index.js +13 -0
  88. package/dist/tools/segment/schemas.js +8 -5
  89. package/dist/tools/store/handlers.d.ts +25 -0
  90. package/dist/tools/store/handlers.js +89 -0
  91. package/dist/tools/store/index.d.ts +55 -0
  92. package/dist/tools/store/index.js +46 -0
  93. package/dist/tools/store/schemas.d.ts +38 -0
  94. package/dist/tools/store/schemas.js +23 -0
  95. package/dist/tools/tierset/handlers.js +92 -1
  96. package/dist/tools/tierset/index.d.ts +6 -0
  97. package/dist/tools/tierset/index.js +8 -1
  98. package/dist/tools/transaction/handlers.js +40 -0
  99. package/dist/tools/transaction/index.d.ts +4 -0
  100. package/dist/tools/transaction/index.js +4 -0
  101. package/dist/tools/transaction/schemas.js +3 -3
  102. package/dist/tools/wallet-type/index.d.ts +4 -0
  103. package/dist/tools/wallet-type/index.js +5 -1
  104. package/dist/tools/webhook/handlers.d.ts +34 -0
  105. package/dist/tools/webhook/handlers.js +147 -0
  106. package/dist/tools/webhook/index.d.ts +97 -0
  107. package/dist/tools/webhook/index.js +65 -0
  108. package/dist/tools/webhook/schemas.d.ts +72 -0
  109. package/dist/tools/{webhook.js → webhook/schemas.js} +0 -140
  110. package/dist/types/schemas/tierset.js +3 -1
  111. package/package.json +1 -1
  112. package/dist/tools/admin.d.ts +0 -165
  113. package/dist/tools/admin.js +0 -205
  114. package/dist/tools/analytics.d.ts +0 -180
  115. package/dist/tools/analytics.js +0 -255
  116. package/dist/tools/apikey.d.ts +0 -79
  117. package/dist/tools/apikey.js +0 -85
  118. package/dist/tools/audit.d.ts +0 -111
  119. package/dist/tools/audit.js +0 -94
  120. package/dist/tools/badge.d.ts +0 -143
  121. package/dist/tools/badge.js +0 -174
  122. package/dist/tools/custom-event.d.ts +0 -315
  123. package/dist/tools/custom-event.js +0 -271
  124. package/dist/tools/export.d.ts +0 -118
  125. package/dist/tools/export.js +0 -152
  126. package/dist/tools/import.d.ts +0 -116
  127. package/dist/tools/import.js +0 -143
  128. package/dist/tools/role.d.ts +0 -180
  129. package/dist/tools/role.js +0 -173
  130. package/dist/tools/store.d.ts +0 -109
  131. package/dist/tools/store.js +0 -125
  132. package/dist/tools/webhook.d.ts +0 -192
@@ -60,6 +60,31 @@ export async function pointsSpend(input) {
60
60
  relatedTool: "ol_points_spend",
61
61
  });
62
62
  }
63
+ const axiosError2 = error;
64
+ if (axiosError2.response?.status === 404) {
65
+ throw new OpenLoyaltyError({
66
+ code: "MEMBER_NOT_FOUND",
67
+ message: `Member '${input.memberId}' not found`,
68
+ hint: `Verify the member ID exists using ol_member_get(memberId: "${input.memberId}"). If not found, use ol_member_list(email: "...") to search by email.`,
69
+ relatedTool: "ol_points_spend",
70
+ });
71
+ }
72
+ if (axiosError2.response?.status === 403) {
73
+ throw new OpenLoyaltyError({
74
+ code: "POINTS_PERMISSION_DENIED",
75
+ message: "You don't have permission to spend member points",
76
+ hint: "Spending points requires the POINTS:MODIFY permission. Use ol_admin_get_permissions() to check your access.",
77
+ relatedTool: "ol_points_spend",
78
+ });
79
+ }
80
+ if (allMessages.includes("points") && (allMessages.includes("positive") || allMessages.includes("greater") || allMessages.includes("zero") || allMessages.includes("negative"))) {
81
+ throw new OpenLoyaltyError({
82
+ code: "INVALID_POINTS_AMOUNT",
83
+ message: "Points amount must be a positive number greater than zero",
84
+ hint: `You requested to spend ${input.points} points. The amount must be a positive integer greater than 0.`,
85
+ relatedTool: "ol_points_spend",
86
+ });
87
+ }
63
88
  throw formatApiError(error, "ol_points_spend");
64
89
  }
65
90
  }
@@ -102,6 +127,39 @@ export async function pointsTransfer(input) {
102
127
  relatedTool: "ol_points_transfer",
103
128
  });
104
129
  }
130
+ const axiosError2 = error;
131
+ if (axiosError2.response?.status === 404) {
132
+ throw new OpenLoyaltyError({
133
+ code: "MEMBER_NOT_FOUND",
134
+ message: "Sender or receiver member not found",
135
+ hint: `Verify both member IDs exist: ol_member_get(memberId: "${input.senderId}") for sender and ol_member_get(memberId: "${input.receiverId}") for receiver. Use ol_member_list() to search by email if needed.`,
136
+ relatedTool: "ol_points_transfer",
137
+ });
138
+ }
139
+ if (axiosError2.response?.status === 403) {
140
+ throw new OpenLoyaltyError({
141
+ code: "POINTS_PERMISSION_DENIED",
142
+ message: "You don't have permission to transfer points between members",
143
+ hint: "Points transfer requires the POINTS:MODIFY permission. Use ol_admin_get_permissions() to check your access.",
144
+ relatedTool: "ol_points_transfer",
145
+ });
146
+ }
147
+ if (allMessages.includes("transfer") && (allMessages.includes("disabled") || allMessages.includes("not allowed") || allMessages.includes("not enabled"))) {
148
+ throw new OpenLoyaltyError({
149
+ code: "TRANSFER_DISABLED",
150
+ message: "Points transfers are not enabled for this store",
151
+ hint: "Point-to-point transfers must be enabled in store settings. Contact a super admin to enable the transfer feature.",
152
+ relatedTool: "ol_points_transfer",
153
+ });
154
+ }
155
+ if (allMessages.includes("points") && (allMessages.includes("positive") || allMessages.includes("greater") || allMessages.includes("zero"))) {
156
+ throw new OpenLoyaltyError({
157
+ code: "INVALID_POINTS_AMOUNT",
158
+ message: "Transfer amount must be a positive number greater than zero",
159
+ hint: `You requested to transfer ${input.points} points. The amount must be a positive integer greater than 0.`,
160
+ relatedTool: "ol_points_transfer",
161
+ });
162
+ }
105
163
  throw formatApiError(error, "ol_points_transfer");
106
164
  }
107
165
  }
@@ -151,6 +209,11 @@ export async function pointsGetBalance(input) {
151
209
  };
152
210
  }
153
211
  catch (error) {
212
+ const axiosError = error;
213
+ if (axiosError.response?.status === 404) {
214
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Member '${input.memberId}' not found`,
215
+ hint: "Use ol_member_list() to search for the member by email, name, or phone.", relatedTool: "ol_points_get_balance" });
216
+ }
154
217
  throw formatApiError(error, "ol_points_get_balance");
155
218
  }
156
219
  }
@@ -188,6 +251,11 @@ export async function pointsGetHistory(input) {
188
251
  };
189
252
  }
190
253
  catch (error) {
254
+ const axiosError = error;
255
+ if (axiosError.response?.status === 404) {
256
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Member '${input.memberId}' not found or no history available`,
257
+ hint: "Use ol_member_list() to verify the member exists.", relatedTool: "ol_points_get_history" });
258
+ }
191
259
  throw formatApiError(error, "ol_points_get_history");
192
260
  }
193
261
  }
@@ -228,6 +296,11 @@ export async function pointsGetHistogram(input) {
228
296
  });
229
297
  }
230
298
  catch (error) {
299
+ const axiosError = error;
300
+ if (axiosError.response?.status === 404) {
301
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Member '${input.memberId}' not found`,
302
+ hint: "Use ol_member_list() to verify the member exists.", relatedTool: "ol_points_get_histogram" });
303
+ }
231
304
  throw formatApiError(error, "ol_points_get_histogram");
232
305
  }
233
306
  }
@@ -6,6 +6,7 @@ export declare const pointsToolDefinitions: readonly [{
6
6
  readonly title: "Add Points to Member";
7
7
  readonly description: string;
8
8
  readonly readOnly: false;
9
+ readonly idempotent: false;
9
10
  readonly inputSchema: {
10
11
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
11
12
  memberId: import("zod").ZodString;
@@ -21,6 +22,7 @@ export declare const pointsToolDefinitions: readonly [{
21
22
  readonly title: "Spend Member Points";
22
23
  readonly description: string;
23
24
  readonly readOnly: false;
25
+ readonly idempotent: false;
24
26
  readonly inputSchema: {
25
27
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
26
28
  memberId: import("zod").ZodString;
@@ -34,6 +36,7 @@ export declare const pointsToolDefinitions: readonly [{
34
36
  readonly title: "Transfer Points Between Members";
35
37
  readonly description: string;
36
38
  readonly readOnly: false;
39
+ readonly idempotent: false;
37
40
  readonly inputSchema: {
38
41
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
39
42
  senderId: import("zod").ZodString;
@@ -46,6 +49,7 @@ export declare const pointsToolDefinitions: readonly [{
46
49
  readonly title: "Check Points Balance";
47
50
  readonly description: string;
48
51
  readonly readOnly: true;
52
+ readonly idempotent: true;
49
53
  readonly inputSchema: {
50
54
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
51
55
  memberId: import("zod").ZodString;
@@ -57,6 +61,7 @@ export declare const pointsToolDefinitions: readonly [{
57
61
  readonly title: "View Points History";
58
62
  readonly description: string;
59
63
  readonly readOnly: true;
64
+ readonly idempotent: true;
60
65
  readonly inputSchema: {
61
66
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
62
67
  memberId: import("zod").ZodString;
@@ -71,6 +76,7 @@ export declare const pointsToolDefinitions: readonly [{
71
76
  readonly title: "View Points Trends";
72
77
  readonly description: string;
73
78
  readonly readOnly: true;
79
+ readonly idempotent: true;
74
80
  readonly inputSchema: {
75
81
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
76
82
  memberId: import("zod").ZodString;
@@ -15,6 +15,7 @@ export const pointsToolDefinitions = [
15
15
  "Confirm amount and reason with user before proceeding. " +
16
16
  "Use for welcome bonuses, goodwill adjustments, or compensation. Returns transferId.",
17
17
  readOnly: false,
18
+ idempotent: false,
18
19
  inputSchema: PointsAddInputSchema,
19
20
  handler: pointsAdd,
20
21
  }, {
@@ -24,6 +25,7 @@ export const pointsToolDefinitions = [
24
25
  "WORKFLOW: Use ol_points_get_balance first to verify available (activeUnits) points before spending. " +
25
26
  "Returns transferId.",
26
27
  readOnly: false,
28
+ idempotent: false,
27
29
  inputSchema: PointsSpendInputSchema,
28
30
  handler: pointsSpend,
29
31
  }, {
@@ -32,6 +34,7 @@ export const pointsToolDefinitions = [
32
34
  description: "Transfer points from one member to another (P2P transfer). " +
33
35
  "Sender must have sufficient balance. Returns transferId.",
34
36
  readOnly: false,
37
+ idempotent: false,
35
38
  inputSchema: PointsTransferInputSchema,
36
39
  handler: pointsTransfer,
37
40
  }, {
@@ -40,6 +43,7 @@ export const pointsToolDefinitions = [
40
43
  description: "Get member points balance breakdown. activeUnits is available for spending. " +
41
44
  "earnedUnits shows lifetime earnings, lockedUnits shows pending points.",
42
45
  readOnly: true,
46
+ idempotent: true,
43
47
  inputSchema: PointsBalanceInputSchema,
44
48
  handler: pointsGetBalance,
45
49
  }, {
@@ -49,6 +53,7 @@ export const pointsToolDefinitions = [
49
53
  "p2p_spending, p2p_adding, blocked, expired. Returns paginated list of transfers. " +
50
54
  "Supports cursor pagination: provide 'cursor' from previous response to get next page.",
51
55
  readOnly: true,
56
+ idempotent: true,
52
57
  inputSchema: PointsHistoryInputSchema,
53
58
  handler: pointsGetHistory,
54
59
  }, {
@@ -57,6 +62,7 @@ export const pointsToolDefinitions = [
57
62
  description: "Get points histogram data for visualization. Shows earning and spending patterns over time. " +
58
63
  "Use interval (day/week/month) to aggregate data and dateFrom/dateTo to filter range.",
59
64
  readOnly: true,
65
+ idempotent: true,
60
66
  inputSchema: PointsHistogramInputSchema,
61
67
  handler: pointsGetHistogram,
62
68
  },
@@ -21,7 +21,7 @@ export const PointsTransferInputSchema = {
21
21
  storeCode: z.string().optional().describe("INTERNAL: Auto-configured from server settings. NEVER ask the user for this value. Only set if the user explicitly requests a different store."),
22
22
  senderId: z.string().describe("The sender member ID (UUID)."),
23
23
  receiverId: z.string().describe("The receiver member ID (UUID)."),
24
- points: z.number().describe("Number of points to transfer."),
24
+ points: z.number().describe("Number of points to transfer (positive integer, e.g., 100). Sender must have sufficient active balance."),
25
25
  };
26
26
  export const PointsBalanceInputSchema = {
27
27
  storeCode: z.string().optional().describe("INTERNAL: Auto-configured from server settings. NEVER ask the user for this value. Only set if the user explicitly requests a different store."),
@@ -6,6 +6,7 @@ export declare const referralToolDefinitions: readonly [{
6
6
  readonly title: "Create Referral Relationship";
7
7
  readonly description: string;
8
8
  readonly readOnly: false;
9
+ readonly idempotent: false;
9
10
  readonly inputSchema: {
10
11
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
11
12
  memberId: import("zod").ZodString;
@@ -17,6 +18,7 @@ export declare const referralToolDefinitions: readonly [{
17
18
  readonly title: "List Referrals";
18
19
  readonly description: string;
19
20
  readonly readOnly: true;
21
+ readonly idempotent: true;
20
22
  readonly inputSchema: {
21
23
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
22
24
  referralId: import("zod").ZodOptional<import("zod").ZodString>;
@@ -36,6 +38,7 @@ export declare const referralToolDefinitions: readonly [{
36
38
  readonly description: string;
37
39
  readonly readOnly: false;
38
40
  readonly destructive: true;
41
+ readonly idempotent: true;
39
42
  readonly inputSchema: {
40
43
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
41
44
  memberId: import("zod").ZodString;
@@ -16,6 +16,7 @@ export const referralToolDefinitions = [
16
16
  "Use this after creating a new member who was referred by an existing member. " +
17
17
  "Both members must exist. A member can only have one referrer.",
18
18
  readOnly: false,
19
+ idempotent: false,
19
20
  inputSchema: ReferralCreateInputSchema,
20
21
  handler: referralCreate,
21
22
  },
@@ -27,6 +28,7 @@ export const referralToolDefinitions = [
27
28
  "Filter by refereeId to find who referred a specific member. " +
28
29
  "Returns referralId, referrerId, referrerName, refereeId, refereeName, and createdAt.",
29
30
  readOnly: true,
31
+ idempotent: true,
30
32
  inputSchema: ReferralListInputSchema,
31
33
  handler: referralList,
32
34
  },
@@ -38,6 +40,7 @@ export const referralToolDefinitions = [
38
40
  "The memberId is the referee (the member who was referred).",
39
41
  readOnly: false,
40
42
  destructive: true,
43
+ idempotent: true,
41
44
  inputSchema: ReferralDeleteInputSchema,
42
45
  handler: referralDelete,
43
46
  },
@@ -117,6 +117,10 @@ export async function rewardGet(input) {
117
117
  return RewardSchema.parse(response);
118
118
  }
119
119
  catch (error) {
120
+ if (error.response?.status === 404) {
121
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Reward '${input.rewardId}' not found`,
122
+ hint: "Use ol_reward_list() to find existing rewards and their IDs.", relatedTool: "ol_reward_get" });
123
+ }
120
124
  throw formatApiError(error, "ol_reward_get");
121
125
  }
122
126
  }
@@ -229,6 +233,10 @@ export async function rewardUpdate(input) {
229
233
  await apiPut(`/${storeCode}/reward/${input.rewardId}`, { reward: payload });
230
234
  }
231
235
  catch (error) {
236
+ if (error.response?.status === 404) {
237
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Reward '${input.rewardId}' not found`,
238
+ hint: "Use ol_reward_list() to find existing rewards and their IDs.", relatedTool: "ol_reward_update" });
239
+ }
232
240
  throw formatApiError(error, "ol_reward_update");
233
241
  }
234
242
  }
@@ -238,6 +246,10 @@ export async function rewardActivate(input) {
238
246
  await apiPost(`/${storeCode}/reward/${input.rewardId}/activate`);
239
247
  }
240
248
  catch (error) {
249
+ if (error.response?.status === 404) {
250
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Reward '${input.rewardId}' not found`,
251
+ hint: "Use ol_reward_list() to find existing rewards and their IDs.", relatedTool: "ol_reward_activate" });
252
+ }
241
253
  throw formatApiError(error, "ol_reward_activate");
242
254
  }
243
255
  }
@@ -247,6 +259,10 @@ export async function rewardDeactivate(input) {
247
259
  await apiPost(`/${storeCode}/reward/${input.rewardId}/deactivate`);
248
260
  }
249
261
  catch (error) {
262
+ if (error.response?.status === 404) {
263
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Reward '${input.rewardId}' not found`,
264
+ hint: "Use ol_reward_list() to find existing rewards and their IDs.", relatedTool: "ol_reward_deactivate" });
265
+ }
250
266
  throw formatApiError(error, "ol_reward_deactivate");
251
267
  }
252
268
  }
@@ -287,21 +303,13 @@ export async function rewardBuy(input) {
287
303
  relatedTool: "ol_reward_buy",
288
304
  });
289
305
  }
290
- if (allMessages.includes("not active") || allMessages.includes("inactive") || allMessages.includes("reward is not active")) {
291
- throw new OpenLoyaltyError({
292
- code: "REWARD_NOT_ACTIVE",
293
- message: "This reward is not active and cannot be purchased",
294
- hint: `Use ol_reward_activate(rewardId: "${input.rewardId}") to activate the reward first, or use ol_reward_get(rewardId: "${input.rewardId}") to check its status.`,
295
- relatedTool: "ol_reward_buy",
296
- });
306
+ if (allMessages.includes("not active") || allMessages.includes("inactive")) {
307
+ throw new OpenLoyaltyError({ code: "REWARD_NOT_ACTIVE", message: "Reward is not active",
308
+ hint: `Use ol_reward_activate(rewardId: "${input.rewardId}") to activate it first.`, relatedTool: "ol_reward_buy" });
297
309
  }
298
310
  if (allMessages.includes("usage limit") || allMessages.includes("limit reached") || allMessages.includes("limit exceeded")) {
299
- throw new OpenLoyaltyError({
300
- code: "USAGE_LIMIT_REACHED",
301
- message: "The usage limit for this reward has been reached",
302
- hint: `Use ol_reward_get(rewardId: "${input.rewardId}") to check usageLimit settings. To increase limits, use ol_reward_update(rewardId: "${input.rewardId}", usageLimitPerUser: N).`,
303
- relatedTool: "ol_reward_buy",
304
- });
311
+ throw new OpenLoyaltyError({ code: "USAGE_LIMIT_REACHED", message: "Usage limit reached for this reward",
312
+ hint: `Use ol_reward_get(rewardId: "${input.rewardId}") to check limits. Use ol_reward_update to increase usageLimitPerUser.`, relatedTool: "ol_reward_buy" });
305
313
  }
306
314
  throw formatApiError(error, "ol_reward_buy");
307
315
  }
@@ -6,6 +6,7 @@ export declare const rewardToolDefinitions: readonly [{
6
6
  readonly title: "Browse Rewards";
7
7
  readonly description: string;
8
8
  readonly readOnly: true;
9
+ readonly idempotent: true;
9
10
  readonly inputSchema: {
10
11
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
11
12
  page: import("zod").ZodOptional<import("zod").ZodNumber>;
@@ -20,6 +21,7 @@ export declare const rewardToolDefinitions: readonly [{
20
21
  readonly title: "Create New Reward";
21
22
  readonly description: string;
22
23
  readonly readOnly: false;
24
+ readonly idempotent: false;
23
25
  readonly inputSchema: {
24
26
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
25
27
  translations: import("zod").ZodObject<{
@@ -87,6 +89,7 @@ export declare const rewardToolDefinitions: readonly [{
87
89
  readonly title: "Get Reward Details";
88
90
  readonly description: "Get full reward details including configuration, targeting, and coupon settings.";
89
91
  readonly readOnly: true;
92
+ readonly idempotent: true;
90
93
  readonly inputSchema: {
91
94
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
92
95
  rewardId: import("zod").ZodString;
@@ -95,6 +98,7 @@ export declare const rewardToolDefinitions: readonly [{
95
98
  }, {
96
99
  readonly name: "ol_reward_update";
97
100
  readonly title: "Update Reward";
101
+ readonly idempotent: true;
98
102
  readonly description: string;
99
103
  readonly readOnly: false;
100
104
  readonly inputSchema: {
@@ -115,6 +119,7 @@ export declare const rewardToolDefinitions: readonly [{
115
119
  readonly title: "Activate Reward";
116
120
  readonly description: "Activate a reward, making it available for members to redeem.";
117
121
  readonly readOnly: false;
122
+ readonly idempotent: true;
118
123
  readonly inputSchema: {
119
124
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
120
125
  rewardId: import("zod").ZodString;
@@ -125,6 +130,7 @@ export declare const rewardToolDefinitions: readonly [{
125
130
  readonly title: "Deactivate Reward";
126
131
  readonly description: "Deactivate a reward, hiding it from members. Already purchased rewards remain valid.";
127
132
  readonly readOnly: false;
133
+ readonly idempotent: true;
128
134
  readonly inputSchema: {
129
135
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
130
136
  rewardId: import("zod").ZodString;
@@ -135,6 +141,7 @@ export declare const rewardToolDefinitions: readonly [{
135
141
  readonly title: "Redeem Reward for Member";
136
142
  readonly description: string;
137
143
  readonly readOnly: false;
144
+ readonly idempotent: false;
138
145
  readonly inputSchema: {
139
146
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
140
147
  rewardId: import("zod").ZodString;
@@ -149,6 +156,7 @@ export declare const rewardToolDefinitions: readonly [{
149
156
  readonly title: "Use Coupon Code";
150
157
  readonly description: string;
151
158
  readonly readOnly: false;
159
+ readonly idempotent: false;
152
160
  readonly inputSchema: {
153
161
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
154
162
  memberId: import("zod").ZodString;
@@ -160,6 +168,7 @@ export declare const rewardToolDefinitions: readonly [{
160
168
  readonly title: "List Reward Categories";
161
169
  readonly description: "List reward categories. Use categoryId when creating or filtering rewards.";
162
170
  readonly readOnly: true;
171
+ readonly idempotent: true;
163
172
  readonly inputSchema: {
164
173
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
165
174
  page: import("zod").ZodOptional<import("zod").ZodNumber>;
@@ -13,6 +13,7 @@ export const rewardToolDefinitions = [
13
13
  description: "List available rewards. Use reward_get for full details or reward_buy to redeem. " +
14
14
  "Filter by type, active status, or category.",
15
15
  readOnly: true,
16
+ idempotent: true,
16
17
  inputSchema: RewardListInputSchema,
17
18
  handler: rewardList,
18
19
  },
@@ -26,8 +27,11 @@ export const rewardToolDefinitions = [
26
27
  "3. Usage limits -- how many redemptions per member? How many total? (0 = unlimited, creates uncapped liability) " +
27
28
  "4. Targeting -- all members, or restricted to specific tiers/segments? " +
28
29
  "REQUIRED: translations.en.name, reward type, activity period, visibility period. " +
29
- "For static_coupon: couponValue is also required.",
30
+ "For static_coupon: couponValue is also required. " +
31
+ "TARGET: Use target='level' (NOT 'tier') for tier targeting, target='segment' for segment targeting, or omit for all members. " +
32
+ "DATETIME: Use 'from'/'to' (NOT 'startsAt'/'endsAt' like campaigns). Format: 'YYYY-MM-DD HH:mm' e.g., '2026-01-01 00:00'.",
30
33
  readOnly: false,
34
+ idempotent: false,
31
35
  inputSchema: RewardCreateInputSchema,
32
36
  handler: rewardCreate,
33
37
  },
@@ -36,12 +40,14 @@ export const rewardToolDefinitions = [
36
40
  title: "Get Reward Details",
37
41
  description: "Get full reward details including configuration, targeting, and coupon settings.",
38
42
  readOnly: true,
43
+ idempotent: true,
39
44
  inputSchema: RewardGetInputSchema,
40
45
  handler: rewardGet,
41
46
  },
42
47
  {
43
48
  name: "ol_reward_update",
44
49
  title: "Update Reward",
50
+ idempotent: true,
45
51
  description: "Update reward configuration. Cannot change reward type after creation. " +
46
52
  "Uses GET-then-PUT pattern (fetches existing reward, merges changes, sends full object). " +
47
53
  "usageLimitPerUser can be set here or at creation time. " +
@@ -55,6 +61,7 @@ export const rewardToolDefinitions = [
55
61
  title: "Activate Reward",
56
62
  description: "Activate a reward, making it available for members to redeem.",
57
63
  readOnly: false,
64
+ idempotent: true,
58
65
  inputSchema: RewardIdInputSchema,
59
66
  handler: rewardActivate,
60
67
  },
@@ -63,6 +70,7 @@ export const rewardToolDefinitions = [
63
70
  title: "Deactivate Reward",
64
71
  description: "Deactivate a reward, hiding it from members. Already purchased rewards remain valid.",
65
72
  readOnly: false,
73
+ idempotent: true,
66
74
  inputSchema: RewardIdInputSchema,
67
75
  handler: rewardDeactivate,
68
76
  },
@@ -72,6 +80,7 @@ export const rewardToolDefinitions = [
72
80
  description: "Purchase reward for member, deducting points. Returns issuedRewardId and couponCode if applicable. " +
73
81
  "Use reward_redeem to mark the coupon as used.",
74
82
  readOnly: false,
83
+ idempotent: false,
75
84
  inputSchema: RewardBuyInputSchema,
76
85
  handler: rewardBuy,
77
86
  },
@@ -81,6 +90,7 @@ export const rewardToolDefinitions = [
81
90
  description: "Mark coupon as used. Validates coupon exists, belongs to member, and is active. " +
82
91
  "Fails if coupon is expired, already used, or doesn't exist.",
83
92
  readOnly: false,
93
+ idempotent: false,
84
94
  inputSchema: RewardRedeemInputSchema,
85
95
  handler: rewardRedeem,
86
96
  },
@@ -89,6 +99,7 @@ export const rewardToolDefinitions = [
89
99
  title: "List Reward Categories",
90
100
  description: "List reward categories. Use categoryId when creating or filtering rewards.",
91
101
  readOnly: true,
102
+ idempotent: true,
92
103
  inputSchema: RewardCategoryListInputSchema,
93
104
  handler: rewardCategoryList,
94
105
  },
@@ -73,9 +73,9 @@ export const RewardGetInputSchema = {
73
73
  export const RewardUpdateInputSchema = {
74
74
  storeCode: z.string().optional().describe("INTERNAL: Auto-configured from server settings. NEVER ask the user for this value. Only set if the user explicitly requests a different store."),
75
75
  rewardId: z.string().describe("The reward ID (UUID) to update."),
76
- name: z.string().optional().describe("Reward name."),
76
+ name: z.string().optional().describe("Reward name (updates translations.en.name internally)."),
77
77
  costInPoints: z.number().optional().describe("Points required to redeem."),
78
- description: z.string().optional().describe("Reward description."),
78
+ description: z.string().optional().describe("Reward description (updates translations.en.description internally)."),
79
79
  active: z.boolean().optional().describe("Whether reward is active."),
80
80
  categories: z.array(z.string()).optional().describe("Array of category UUIDs. Use ol_reward_category_list() to find valid categoryId values."),
81
81
  levels: z.array(z.string()).optional().describe("Array of tier level UUIDs. Use ol_tierset_get_tiers(tierSetId) to find valid levelId values."),
@@ -0,0 +1,35 @@
1
+ import { Role, RoleListItem, ACLResource } from "../../types/schemas/role.js";
2
+ import type { RolePermissionInput } from "./schemas.js";
3
+ export declare function roleList(input: {
4
+ page?: number;
5
+ perPage?: number;
6
+ name?: string;
7
+ master?: boolean;
8
+ default?: boolean;
9
+ }): Promise<{
10
+ items: RoleListItem[];
11
+ total?: Record<string, unknown>;
12
+ }>;
13
+ export declare function roleCreate(input: {
14
+ name: string;
15
+ permissions?: RolePermissionInput[];
16
+ stores?: string[];
17
+ default?: boolean;
18
+ }): Promise<void>;
19
+ export declare function roleGet(input: {
20
+ roleId: string | number;
21
+ }): Promise<Role>;
22
+ export declare function roleUpdate(input: {
23
+ roleId: string | number;
24
+ name?: string;
25
+ permissions?: RolePermissionInput[];
26
+ stores?: string[];
27
+ default?: boolean;
28
+ }): Promise<void>;
29
+ export declare function roleDelete(input: {
30
+ roleId: string | number;
31
+ }): Promise<void>;
32
+ export declare function aclGetResources(): Promise<{
33
+ items: ACLResource[];
34
+ total?: Record<string, unknown>;
35
+ }>;
@@ -0,0 +1,127 @@
1
+ import { apiGet, apiPost, apiPut, apiDelete } from "../../client/http.js";
2
+ import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
3
+ import axios from "axios";
4
+ export async function roleList(input) {
5
+ const params = new URLSearchParams();
6
+ if (input.page)
7
+ params.append("_page", String(input.page));
8
+ if (input.perPage)
9
+ params.append("_itemsOnPage", String(input.perPage));
10
+ if (input.name)
11
+ params.append("name", input.name);
12
+ if (input.master !== undefined)
13
+ params.append("master", String(input.master));
14
+ if (input.default !== undefined)
15
+ params.append("default", String(input.default));
16
+ const queryString = params.toString();
17
+ const url = `/acl/role${queryString ? `?${queryString}` : ""}`;
18
+ try {
19
+ const response = await apiGet(url);
20
+ return response;
21
+ }
22
+ catch (error) {
23
+ if (axios.isAxiosError(error) && error.response?.status === 403) {
24
+ throw new OpenLoyaltyError({
25
+ code: "ROLE_PERMISSION_DENIED",
26
+ message: "You don't have permission to view roles",
27
+ hint: "Role management requires super admin access or the ROLE:VIEW permission. Use ol_admin_get_permissions() to check your access.",
28
+ relatedTool: "ol_role_list",
29
+ });
30
+ }
31
+ throw formatApiError(error, "ol_role_list");
32
+ }
33
+ }
34
+ export async function roleCreate(input) {
35
+ const payload = {
36
+ name: input.name,
37
+ };
38
+ if (input.permissions)
39
+ payload.permissions = input.permissions;
40
+ if (input.stores)
41
+ payload.stores = input.stores;
42
+ if (input.default !== undefined)
43
+ payload.default = input.default;
44
+ try {
45
+ await apiPost("/acl/role", { role: payload });
46
+ }
47
+ catch (error) {
48
+ if (axios.isAxiosError(error)) {
49
+ const allMessages = [error.response?.data?.message || "", ...(error.response?.data?.errors || []).map((e) => e.message)].join(" ").toLowerCase();
50
+ if (allMessages.includes("name") && (allMessages.includes("already") || allMessages.includes("unique") || allMessages.includes("exists"))) {
51
+ throw new OpenLoyaltyError({ code: "DUPLICATE_NAME", message: `Role with name '${input.name}' already exists`,
52
+ hint: "Use ol_role_list() to find existing roles, or choose a different name.", relatedTool: "ol_role_create" });
53
+ }
54
+ }
55
+ throw formatApiError(error, "ol_role_create");
56
+ }
57
+ }
58
+ export async function roleGet(input) {
59
+ try {
60
+ const response = await apiGet(`/acl/role/${input.roleId}`);
61
+ return response;
62
+ }
63
+ catch (error) {
64
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
65
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Role '${input.roleId}' not found`,
66
+ hint: "Use ol_role_list() to find existing roles and their IDs.", relatedTool: "ol_role_get" });
67
+ }
68
+ throw formatApiError(error, "ol_role_get");
69
+ }
70
+ }
71
+ export async function roleUpdate(input) {
72
+ const payload = {};
73
+ if (input.name)
74
+ payload.name = input.name;
75
+ if (input.permissions)
76
+ payload.permissions = input.permissions;
77
+ if (input.stores)
78
+ payload.stores = input.stores;
79
+ if (input.default !== undefined)
80
+ payload.default = input.default;
81
+ try {
82
+ await apiPut(`/acl/role/${input.roleId}`, { role: payload });
83
+ }
84
+ catch (error) {
85
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
86
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Role '${input.roleId}' not found`,
87
+ hint: "Use ol_role_list() to find existing roles and their IDs.", relatedTool: "ol_role_update" });
88
+ }
89
+ throw formatApiError(error, "ol_role_update");
90
+ }
91
+ }
92
+ export async function roleDelete(input) {
93
+ try {
94
+ await apiDelete(`/acl/role/${input.roleId}`);
95
+ }
96
+ catch (error) {
97
+ if (axios.isAxiosError(error)) {
98
+ if (error.response?.status === 404) {
99
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Role '${input.roleId}' not found`,
100
+ hint: "Use ol_role_list() to find existing roles and their IDs.", relatedTool: "ol_role_delete" });
101
+ }
102
+ const allMessages = [error.response?.data?.message || "", ...(error.response?.data?.errors || []).map((e) => e.message)].join(" ").toLowerCase();
103
+ if (allMessages.includes("in use") || allMessages.includes("assigned") || allMessages.includes("cannot delete")) {
104
+ throw new OpenLoyaltyError({ code: "ROLE_IN_USE", message: `Role '${input.roleId}' is assigned to admin users and cannot be deleted`,
105
+ hint: "Remove this role from all admin users first using ol_admin_update(), then retry deletion.", relatedTool: "ol_role_delete" });
106
+ }
107
+ }
108
+ throw formatApiError(error, "ol_role_delete");
109
+ }
110
+ }
111
+ export async function aclGetResources() {
112
+ try {
113
+ const response = await apiGet("/acl/resources");
114
+ return response;
115
+ }
116
+ catch (error) {
117
+ if (axios.isAxiosError(error) && error.response?.status === 403) {
118
+ throw new OpenLoyaltyError({
119
+ code: "ACL_PERMISSION_DENIED",
120
+ message: "You don't have permission to view ACL resources",
121
+ hint: "ACL resource listing requires super admin access or the ROLE:VIEW permission. Use ol_admin_get_permissions() to check your access.",
122
+ relatedTool: "ol_acl_get_resources",
123
+ });
124
+ }
125
+ throw formatApiError(error, "ol_acl_get_resources");
126
+ }
127
+ }