@open-loyalty/mcp-server 1.3.3 → 1.3.4

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.
@@ -80,9 +80,14 @@ export async function pointsSpend(input) {
80
80
  return { transferId: response.transferId };
81
81
  }
82
82
  catch (error) {
83
- // Check for insufficient points error
83
+ // Check for insufficient points error - API returns various formats:
84
+ // "NotEnoughPoints", "insufficient", "not enough points" (lowercase)
85
+ // Must check both error.message AND axios response data
84
86
  const errorMessage = error instanceof Error ? error.message : String(error);
85
- if (errorMessage.includes("NotEnoughPoints") || errorMessage.includes("insufficient")) {
87
+ const axiosError = error;
88
+ const responseMessage = axiosError.response?.data?.message || "";
89
+ const combinedMessage = `${errorMessage} ${responseMessage}`.toLowerCase();
90
+ if (combinedMessage.includes("notenoughpoints") || combinedMessage.includes("insufficient") || combinedMessage.includes("not enough points")) {
86
91
  throw new OpenLoyaltyError({
87
92
  code: "INSUFFICIENT_BALANCE",
88
93
  message: "Member does not have enough points to complete this operation",
@@ -110,8 +115,13 @@ export async function pointsTransfer(input) {
110
115
  return { transferId: response.transferId };
111
116
  }
112
117
  catch (error) {
118
+ // Check for insufficient points error - API returns various formats
119
+ // Must check both error.message AND axios response data
113
120
  const errorMessage = error instanceof Error ? error.message : String(error);
114
- if (errorMessage.includes("NotEnoughPoints") || errorMessage.includes("insufficient")) {
121
+ const axiosError = error;
122
+ const responseMessage = axiosError.response?.data?.message || "";
123
+ const combinedMessage = `${errorMessage} ${responseMessage}`.toLowerCase();
124
+ if (combinedMessage.includes("notenoughpoints") || combinedMessage.includes("insufficient") || combinedMessage.includes("not enough points")) {
115
125
  throw new OpenLoyaltyError({
116
126
  code: "INSUFFICIENT_BALANCE",
117
127
  message: "Sender does not have enough points to transfer",
@@ -132,10 +132,11 @@ export async function rewardUpdate(input) {
132
132
  visibilityPayload.to = existingVisibility.to;
133
133
  payload.visibility = visibilityPayload;
134
134
  }
135
- // usageLimit: Required field, but only include perUser (API rejects extra fields)
135
+ // usageLimit: BOTH perUser AND general are REQUIRED by the API
136
136
  // The GET response may include derived fields that PUT doesn't accept
137
137
  payload.usageLimit = {
138
138
  perUser: existingUsageLimit?.perUser ?? 1,
139
+ general: existingUsageLimit?.general ?? 10000,
139
140
  };
140
141
  // Coupon-specific fields (required for static_coupon, dynamic_coupon, conversion_coupon)
141
142
  if (existing.couponValue !== undefined)
@@ -212,8 +213,14 @@ export async function rewardBuy(input) {
212
213
  };
213
214
  }
214
215
  catch (error) {
216
+ // Check for insufficient points error - API returns various formats:
217
+ // "NotEnoughPoints", "insufficient", "not enough points" (lowercase)
218
+ // Must check both error.message AND axios response data
215
219
  const errorMessage = error instanceof Error ? error.message : String(error);
216
- if (errorMessage.includes("NotEnoughPoints") || errorMessage.includes("insufficient")) {
220
+ const axiosError = error;
221
+ const responseMessage = axiosError.response?.data?.message || "";
222
+ const combinedMessage = `${errorMessage} ${responseMessage}`.toLowerCase();
223
+ if (combinedMessage.includes("notenoughpoints") || combinedMessage.includes("insufficient") || combinedMessage.includes("not enough points")) {
217
224
  throw new OpenLoyaltyError({
218
225
  code: "INSUFFICIENT_BALANCE",
219
226
  message: "Member does not have enough points to purchase this reward",
@@ -19,17 +19,6 @@ export declare const TierSetCreateInputSchema: {
19
19
  attribute: "activeUnits" | "totalEarnedUnits" | "totalSpending" | "monthsSinceJoiningProgram" | "cumulatedEarnedUnits";
20
20
  walletType?: string | undefined;
21
21
  }>, "many">;
22
- downgrade: z.ZodOptional<z.ZodObject<{
23
- mode: z.ZodEnum<["none", "automatic", "x_days"]>;
24
- days: z.ZodOptional<z.ZodNumber>;
25
- }, "strip", z.ZodTypeAny, {
26
- mode: "none" | "automatic" | "x_days";
27
- days?: number | undefined;
28
- }, {
29
- mode: "none" | "automatic" | "x_days";
30
- days?: number | undefined;
31
- }>>;
32
- active: z.ZodOptional<z.ZodBoolean>;
33
22
  };
34
23
  export declare const TierSetGetInputSchema: {
35
24
  storeCode: z.ZodOptional<z.ZodString>;
@@ -107,11 +96,6 @@ type TierSetCreateInput = {
107
96
  attribute: string;
108
97
  walletType?: string;
109
98
  }[];
110
- downgrade?: {
111
- mode: string;
112
- days?: number;
113
- };
114
- active?: boolean;
115
99
  };
116
100
  type TierSetGetInput = {
117
101
  storeCode?: string;
@@ -196,17 +180,6 @@ export declare const tiersetToolDefinitions: readonly [{
196
180
  attribute: "activeUnits" | "totalEarnedUnits" | "totalSpending" | "monthsSinceJoiningProgram" | "cumulatedEarnedUnits";
197
181
  walletType?: string | undefined;
198
182
  }>, "many">;
199
- downgrade: z.ZodOptional<z.ZodObject<{
200
- mode: z.ZodEnum<["none", "automatic", "x_days"]>;
201
- days: z.ZodOptional<z.ZodNumber>;
202
- }, "strip", z.ZodTypeAny, {
203
- mode: "none" | "automatic" | "x_days";
204
- days?: number | undefined;
205
- }, {
206
- mode: "none" | "automatic" | "x_days";
207
- days?: number | undefined;
208
- }>>;
209
- active: z.ZodOptional<z.ZodBoolean>;
210
183
  };
211
184
  readonly handler: typeof tiersetCreate;
212
185
  }, {
@@ -20,11 +20,7 @@ export const TierSetCreateInputSchema = {
20
20
  walletType: z.string().optional().describe("Wallet type CODE (not UUID). Required for unit-based attributes (activeUnits, totalEarnedUnits, cumulatedEarnedUnits). " +
21
21
  "Use wallet_type_list to find walletType.code (e.g., 'default')."),
22
22
  })).describe("Array of conditions that define tier progression criteria. IMPORTANT: Use 'totalEarnedUnits' for lifetime points (NOT 'earnedUnits')."),
23
- downgrade: z.object({
24
- mode: DowngradeModeEnum.describe("Downgrade mode."),
25
- days: z.number().optional().describe("Number of days for x_days mode (required if mode is x_days)."),
26
- }).optional().describe("Downgrade configuration for the tier set."),
27
- active: z.boolean().optional().describe("Whether the tier set is active. Defaults to true."),
23
+ // NOTE: downgrade and active are NOT supported at creation time - use tierset_update after creation
28
24
  };
29
25
  export const TierSetGetInputSchema = {
30
26
  storeCode: z.string().optional().describe("Store code for multi-tenant routing. DO NOT pass this parameter - the configured default will be used automatically. Only provide a value if the user explicitly asks to work with a different store."),
@@ -87,25 +83,34 @@ export async function tiersetList(input) {
87
83
  }
88
84
  export async function tiersetCreate(input) {
89
85
  const storeCode = getStoreCode(input.storeCode);
90
- // API requires tierSet wrapper with translations (at least en is required)
91
- // Also include empty tiers array - some API versions require it
92
- const payload = {
93
- tierSet: {
94
- translations: {
95
- en: {
96
- name: input.name,
97
- description: input.description || "",
98
- },
86
+ // API QUIRK: Only accepts specific fields at creation time.
87
+ // Fields NOT supported at creation: tiers, active, downgrade
88
+ // These must be set via tierset_update or tierset_update_tiers after creation.
89
+ //
90
+ // REQUIRED fields:
91
+ // - translations.en.name (string)
92
+ // - conditions (array with attribute, optionally walletType)
93
+ const tierSetPayload = {
94
+ translations: {
95
+ en: {
96
+ name: input.name,
97
+ description: input.description || "",
99
98
  },
100
- conditions: input.conditions,
101
- tiers: [], // Required by API - tiers are added separately via tierset_update_tiers
102
- downgrade: input.downgrade,
103
- active: input.active ?? true,
104
99
  },
100
+ conditions: input.conditions,
105
101
  };
102
+ const payload = { tierSet: tierSetPayload };
106
103
  try {
107
104
  const response = await apiPost(`/${storeCode}/tierSet`, payload);
108
- const validated = TierSetSchema.parse(response);
105
+ // API QUIRK: Create endpoint only returns { tierSetId: string }
106
+ // We need to fetch the full tier set to get the conditionId values
107
+ const createResponse = response;
108
+ if (!createResponse.tierSetId) {
109
+ throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
110
+ }
111
+ // Fetch the created tier set to get condition IDs
112
+ const tierSetResponse = await apiGet(`/${storeCode}/tierSet/${createResponse.tierSetId}`);
113
+ const validated = TierSetSchema.parse(tierSetResponse);
109
114
  return {
110
115
  tierSetId: validated.tierSetId,
111
116
  conditions: validated.conditions.map((c) => ({
@@ -228,7 +233,9 @@ export const tiersetToolDefinitions = [
228
233
  description: "Create a new tier set (loyalty program structure). " +
229
234
  "⚠️ LIMIT: Maximum 3 ACTIVE tier sets per store. ALWAYS call tierset_list FIRST to check existing tier sets - if one exists that matches your needs, REUSE IT instead of creating a new one. " +
230
235
  "WORKFLOW: 1) tierset_list (check existing) → 2) tierset_create (only if needed) → 3) tierset_get (to get conditionId) → 4) tierset_update_tiers (to define thresholds). " +
231
- "Valid attributes: 'totalEarnedUnits' (lifetime points), 'activeUnits' (current balance), 'totalSpending', 'monthsSinceJoiningProgram'. " +
236
+ "⚠️ API LIMITATION: Only 'name', 'description', and 'conditions' are accepted at creation time. " +
237
+ "Fields like 'active', 'downgrade', 'tiers' are NOT supported at creation - use tierset_update after creation to set these. " +
238
+ "Valid condition attributes: 'totalEarnedUnits' (lifetime points), 'activeUnits' (current balance), 'totalSpending', 'monthsSinceJoiningProgram'. " +
232
239
  "COMMON MISTAKE: Use 'totalEarnedUnits' NOT 'earnedUnits' for lifetime points. " +
233
240
  "For unit-based attributes, set walletType to 'default'.",
234
241
  readOnly: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-loyalty/mcp-server",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "type": "module",
5
5
  "description": "MCP server for Open Loyalty API - enables AI agents to manage loyalty programs, members, points, rewards, and transactions",
6
6
  "author": "Marcin Dyguda <md@openloyalty.io>",