@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
@@ -0,0 +1,89 @@
1
+ import { apiGet, apiPost, apiPut } from "../../client/http.js";
2
+ import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
3
+ import axios from "axios";
4
+ export async function storeList(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.active !== undefined)
11
+ params.append("active[eq]", String(input.active));
12
+ if (input.name)
13
+ params.append("name[eq]", input.name);
14
+ if (input.code)
15
+ params.append("code", input.code);
16
+ const queryString = params.toString();
17
+ const url = `/store${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: "STORE_PERMISSION_DENIED",
26
+ message: "You don't have permission to list stores",
27
+ hint: "Store management requires the STORE:VIEW permission or super admin access. Use ol_admin_get_permissions() to check your access.",
28
+ relatedTool: "ol_store_list",
29
+ });
30
+ }
31
+ throw formatApiError(error, "ol_store_list");
32
+ }
33
+ }
34
+ export async function storeCreate(input) {
35
+ const payload = {
36
+ code: input.code,
37
+ name: input.name,
38
+ };
39
+ if (input.currency)
40
+ payload.currency = input.currency;
41
+ if (input.active !== undefined)
42
+ payload.active = input.active;
43
+ try {
44
+ const response = await apiPost("/store", { store: payload });
45
+ return response;
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("code") && (allMessages.includes("already") || allMessages.includes("unique") || allMessages.includes("exists"))) {
51
+ throw new OpenLoyaltyError({ code: "DUPLICATE_CODE", message: `Store with code '${input.code}' already exists`,
52
+ hint: `Use ol_store_list(code: "${input.code}") to find the existing store, or choose a different code.`, relatedTool: "ol_store_create" });
53
+ }
54
+ }
55
+ throw formatApiError(error, "ol_store_create");
56
+ }
57
+ }
58
+ export async function storeGet(input) {
59
+ try {
60
+ const response = await apiGet(`/store/${input.storeId}`);
61
+ return response;
62
+ }
63
+ catch (error) {
64
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
65
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Store '${input.storeId}' not found`,
66
+ hint: "Use ol_store_list() to find existing stores and their IDs.", relatedTool: "ol_store_get" });
67
+ }
68
+ throw formatApiError(error, "ol_store_get");
69
+ }
70
+ }
71
+ export async function storeUpdate(input) {
72
+ const payload = {};
73
+ if (input.name)
74
+ payload.name = input.name;
75
+ if (input.active !== undefined)
76
+ payload.active = input.active;
77
+ if (input.currency)
78
+ payload.currency = input.currency;
79
+ try {
80
+ await apiPut(`/store/${input.storeId}`, { store: payload });
81
+ }
82
+ catch (error) {
83
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
84
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Store '${input.storeId}' not found`,
85
+ hint: "Use ol_store_list() to find existing stores and their IDs.", relatedTool: "ol_store_update" });
86
+ }
87
+ throw formatApiError(error, "ol_store_update");
88
+ }
89
+ }
@@ -0,0 +1,55 @@
1
+ export { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, } from "./schemas.js";
2
+ export type { Store, StoreListResponse } from "./schemas.js";
3
+ export { storeList, storeCreate, storeGet, storeUpdate, } from "./handlers.js";
4
+ import { storeList, storeCreate, storeGet, storeUpdate } from "./handlers.js";
5
+ export declare const storeToolDefinitions: readonly [{
6
+ readonly name: "ol_store_list";
7
+ readonly title: "List Stores";
8
+ readonly description: "List all stores with optional filtering. Returns paginated list of stores with storeId, code, name, currency, and active status. Stores enable multi-tenant loyalty programs. Each store has independent members, campaigns, and settings.";
9
+ readonly readOnly: true;
10
+ readonly idempotent: true;
11
+ readonly inputSchema: {
12
+ page: import("zod").ZodOptional<import("zod").ZodNumber>;
13
+ perPage: import("zod").ZodOptional<import("zod").ZodNumber>;
14
+ active: import("zod").ZodOptional<import("zod").ZodBoolean>;
15
+ name: import("zod").ZodOptional<import("zod").ZodString>;
16
+ code: import("zod").ZodOptional<import("zod").ZodString>;
17
+ };
18
+ readonly handler: typeof storeList;
19
+ }, {
20
+ readonly name: "ol_store_create";
21
+ readonly title: "Create Store";
22
+ readonly description: "Create a new store for multi-tenant setup. Requires unique code and name. Returns storeId on success.";
23
+ readonly readOnly: false;
24
+ readonly idempotent: false;
25
+ readonly inputSchema: {
26
+ code: import("zod").ZodString;
27
+ name: import("zod").ZodString;
28
+ currency: import("zod").ZodOptional<import("zod").ZodString>;
29
+ active: import("zod").ZodOptional<import("zod").ZodBoolean>;
30
+ };
31
+ readonly handler: typeof storeCreate;
32
+ }, {
33
+ readonly name: "ol_store_get";
34
+ readonly title: "Get Store Details";
35
+ readonly description: "Get full store configuration by ID. Returns storeId, code, name, currency, active status, and timestamps.";
36
+ readonly readOnly: true;
37
+ readonly idempotent: true;
38
+ readonly inputSchema: {
39
+ storeId: import("zod").ZodString;
40
+ };
41
+ readonly handler: typeof storeGet;
42
+ }, {
43
+ readonly name: "ol_store_update";
44
+ readonly title: "Update Store";
45
+ readonly description: "Update store configuration. Can update name, currency, and active status. Returns void on success (204 No Content).";
46
+ readonly readOnly: false;
47
+ readonly idempotent: true;
48
+ readonly inputSchema: {
49
+ storeId: import("zod").ZodString;
50
+ name: import("zod").ZodOptional<import("zod").ZodString>;
51
+ active: import("zod").ZodOptional<import("zod").ZodBoolean>;
52
+ currency: import("zod").ZodOptional<import("zod").ZodString>;
53
+ };
54
+ readonly handler: typeof storeUpdate;
55
+ }];
@@ -0,0 +1,46 @@
1
+ // Re-export schemas and types
2
+ export { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, } from "./schemas.js";
3
+ // Re-export handlers
4
+ export { storeList, storeCreate, storeGet, storeUpdate, } from "./handlers.js";
5
+ // Imports for tool definitions
6
+ import { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, } from "./schemas.js";
7
+ import { storeList, storeCreate, storeGet, storeUpdate, } from "./handlers.js";
8
+ // Tool definitions
9
+ export const storeToolDefinitions = [
10
+ {
11
+ name: "ol_store_list",
12
+ title: "List Stores",
13
+ description: "List all stores with optional filtering. Returns paginated list of stores with storeId, code, name, currency, and active status. Stores enable multi-tenant loyalty programs. Each store has independent members, campaigns, and settings.",
14
+ readOnly: true,
15
+ idempotent: true,
16
+ inputSchema: StoreListInputSchema,
17
+ handler: storeList,
18
+ },
19
+ {
20
+ name: "ol_store_create",
21
+ title: "Create Store",
22
+ description: "Create a new store for multi-tenant setup. Requires unique code and name. Returns storeId on success.",
23
+ readOnly: false,
24
+ idempotent: false,
25
+ inputSchema: StoreCreateInputSchema,
26
+ handler: storeCreate,
27
+ },
28
+ {
29
+ name: "ol_store_get",
30
+ title: "Get Store Details",
31
+ description: "Get full store configuration by ID. Returns storeId, code, name, currency, active status, and timestamps.",
32
+ readOnly: true,
33
+ idempotent: true,
34
+ inputSchema: StoreGetInputSchema,
35
+ handler: storeGet,
36
+ },
37
+ {
38
+ name: "ol_store_update",
39
+ title: "Update Store",
40
+ description: "Update store configuration. Can update name, currency, and active status. Returns void on success (204 No Content).",
41
+ readOnly: false,
42
+ idempotent: true,
43
+ inputSchema: StoreUpdateInputSchema,
44
+ handler: storeUpdate,
45
+ },
46
+ ];
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ export interface Store {
3
+ storeId: string;
4
+ code: string;
5
+ currency: string;
6
+ name: string;
7
+ active: boolean;
8
+ createdBy?: string;
9
+ createdAt?: string;
10
+ updatedBy?: string;
11
+ updatedAt?: string;
12
+ }
13
+ export interface StoreListResponse {
14
+ items: Store[];
15
+ total?: Record<string, unknown>;
16
+ }
17
+ export declare const StoreListInputSchema: {
18
+ page: z.ZodOptional<z.ZodNumber>;
19
+ perPage: z.ZodOptional<z.ZodNumber>;
20
+ active: z.ZodOptional<z.ZodBoolean>;
21
+ name: z.ZodOptional<z.ZodString>;
22
+ code: z.ZodOptional<z.ZodString>;
23
+ };
24
+ export declare const StoreCreateInputSchema: {
25
+ code: z.ZodString;
26
+ name: z.ZodString;
27
+ currency: z.ZodOptional<z.ZodString>;
28
+ active: z.ZodOptional<z.ZodBoolean>;
29
+ };
30
+ export declare const StoreGetInputSchema: {
31
+ storeId: z.ZodString;
32
+ };
33
+ export declare const StoreUpdateInputSchema: {
34
+ storeId: z.ZodString;
35
+ name: z.ZodOptional<z.ZodString>;
36
+ active: z.ZodOptional<z.ZodBoolean>;
37
+ currency: z.ZodOptional<z.ZodString>;
38
+ };
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ export const StoreListInputSchema = {
3
+ page: z.number().optional().describe("Page number (default: 1)."),
4
+ perPage: z.number().optional().describe("Items per page (default: 25)."),
5
+ active: z.boolean().optional().describe("Filter by active status."),
6
+ name: z.string().optional().describe("Filter by store name."),
7
+ code: z.string().optional().describe("Filter by store code."),
8
+ };
9
+ export const StoreCreateInputSchema = {
10
+ code: z.string().describe("Store code (required, unique identifier like 'US', 'EU')."),
11
+ name: z.string().describe("Store name (required)."),
12
+ currency: z.string().optional().describe("Currency code (e.g., 'USD', 'EUR')."),
13
+ active: z.boolean().optional().describe("Whether the store is active (default: true)."),
14
+ };
15
+ export const StoreGetInputSchema = {
16
+ storeId: z.string().describe("The store ID (UUID) to retrieve."),
17
+ };
18
+ export const StoreUpdateInputSchema = {
19
+ storeId: z.string().describe("The store ID (UUID) to update."),
20
+ name: z.string().optional().describe("Store name."),
21
+ active: z.boolean().optional().describe("Whether the store is active."),
22
+ currency: z.string().optional().describe("Currency code."),
23
+ };
@@ -26,6 +26,23 @@ export async function tiersetList(input) {
26
26
  };
27
27
  }
28
28
  catch (error) {
29
+ const axiosError = error;
30
+ if (axiosError.response?.status === 404) {
31
+ throw new OpenLoyaltyError({
32
+ code: "STORE_NOT_FOUND",
33
+ message: "Cannot list tier sets - store not found",
34
+ hint: "Use ol_store_list() to verify the store code is correct.",
35
+ relatedTool: "ol_tierset_list",
36
+ });
37
+ }
38
+ if (axiosError.response?.status === 403) {
39
+ throw new OpenLoyaltyError({
40
+ code: "TIERSET_PERMISSION_DENIED",
41
+ message: "You don't have permission to view tier sets",
42
+ hint: "Tier set management requires the LEVEL:VIEW permission. Use ol_admin_get_permissions() to check your access.",
43
+ relatedTool: "ol_tierset_list",
44
+ });
45
+ }
29
46
  throw formatApiError(error, "ol_tierset_list");
30
47
  }
31
48
  }
@@ -70,7 +87,12 @@ export async function tiersetCreate(input) {
70
87
  // We need to fetch the full tier set to get the conditionId values
71
88
  const createResponse = response;
72
89
  if (!createResponse.tierSetId) {
73
- throw new Error(`Unexpected response format: ${JSON.stringify(response)}`);
90
+ throw new OpenLoyaltyError({
91
+ code: "UNEXPECTED_RESPONSE",
92
+ message: `Unexpected response format from tier set creation: ${JSON.stringify(response)}`,
93
+ hint: "The API returned an unexpected response. This is likely a temporary issue or an MCP implementation bug. Try again, or use ol_tierset_list() to check if the tier set was created.",
94
+ relatedTool: "ol_tierset_create",
95
+ });
74
96
  }
75
97
  // Fetch the created tier set to get condition IDs
76
98
  const tierSetResponse = await apiGet(`/${storeCode}/tierSet/${createResponse.tierSetId}`);
@@ -109,6 +131,15 @@ export async function tiersetGet(input) {
109
131
  return validated;
110
132
  }
111
133
  catch (error) {
134
+ const axiosError = error;
135
+ if (axiosError.response?.status === 404) {
136
+ throw new OpenLoyaltyError({
137
+ code: "NOT_FOUND",
138
+ message: `Tier set '${input.tierSetId}' not found`,
139
+ hint: "Use ol_tierset_list() to find existing tier sets and their IDs.",
140
+ relatedTool: "ol_tierset_get",
141
+ });
142
+ }
112
143
  throw formatApiError(error, "ol_tierset_get");
113
144
  }
114
145
  }
@@ -122,6 +153,15 @@ export async function tiersetUpdate(input) {
122
153
  existingTierSet = TierSetSchema.parse(response);
123
154
  }
124
155
  catch (error) {
156
+ const axiosError = error;
157
+ if (axiosError.response?.status === 404) {
158
+ throw new OpenLoyaltyError({
159
+ code: "NOT_FOUND",
160
+ message: `Tier set '${input.tierSetId}' not found`,
161
+ hint: "Use ol_tierset_list() to find existing tier sets and their IDs.",
162
+ relatedTool: "ol_tierset_update",
163
+ });
164
+ }
125
165
  throw formatApiError(error, "ol_tierset_update");
126
166
  }
127
167
  // Build payload using existing values as defaults
@@ -148,6 +188,26 @@ export async function tiersetUpdate(input) {
148
188
  await apiPut(`/${storeCode}/tierSet/${input.tierSetId}`, { tierSet: tierSetPayload });
149
189
  }
150
190
  catch (error) {
191
+ const axiosError = error;
192
+ if (axiosError.response?.status === 404) {
193
+ throw new OpenLoyaltyError({
194
+ code: "NOT_FOUND",
195
+ message: `Tier set '${input.tierSetId}' not found`,
196
+ hint: "The tier set may have been deleted after it was read. Use ol_tierset_list() to verify it still exists.",
197
+ relatedTool: "ol_tierset_update",
198
+ });
199
+ }
200
+ if (axiosError.response?.status === 400) {
201
+ const allMessages = [axiosError.response?.data?.message || "", ...(axiosError.response?.data?.errors || []).map(e => e.message)].join(" ").toLowerCase();
202
+ if (allMessages.includes("active") && allMessages.includes("maximum")) {
203
+ throw new OpenLoyaltyError({
204
+ code: "TIER_SET_LIMIT",
205
+ message: "Cannot activate tier set - maximum 3 active tier sets per store",
206
+ hint: "Use ol_tierset_list() to see existing active tier sets. Deactivate one before activating this one.",
207
+ relatedTool: "ol_tierset_update",
208
+ });
209
+ }
210
+ }
151
211
  throw formatApiError(error, "ol_tierset_update");
152
212
  }
153
213
  }
@@ -174,6 +234,28 @@ export async function tiersetUpdateTiers(input) {
174
234
  await apiPut(`/${storeCode}/tierSet/${input.tierSetId}/tiers`, payload);
175
235
  }
176
236
  catch (error) {
237
+ const axiosError = error;
238
+ const apiErrors = axiosError.response?.data?.errors || [];
239
+ const allMessages = [
240
+ axiosError.response?.data?.message || "",
241
+ ...apiErrors.map(e => e.message)
242
+ ].join(" ").toLowerCase();
243
+ if (allMessages.includes("condition") && (allMessages.includes("not found") || allMessages.includes("invalid") || allMessages.includes("does not exist"))) {
244
+ throw new OpenLoyaltyError({
245
+ code: "INVALID_CONDITION_ID",
246
+ message: "Invalid conditionId in tier configuration",
247
+ hint: `Use ol_tierset_get(tierSetId: "${input.tierSetId}") to find valid conditionId values from conditions[].id. Use the SAME conditionId for ALL tiers.`,
248
+ relatedTool: "ol_tierset_update_tiers",
249
+ });
250
+ }
251
+ if (axiosError.response?.status === 404) {
252
+ throw new OpenLoyaltyError({
253
+ code: "NOT_FOUND",
254
+ message: `Tier set '${input.tierSetId}' not found`,
255
+ hint: "Use ol_tierset_list() to find existing tier sets and their IDs.",
256
+ relatedTool: "ol_tierset_update_tiers",
257
+ });
258
+ }
177
259
  throw formatApiError(error, "ol_tierset_update_tiers");
178
260
  }
179
261
  }
@@ -191,6 +273,15 @@ export async function tiersetGetTiers(input) {
191
273
  };
192
274
  }
193
275
  catch (error) {
276
+ const axiosError = error;
277
+ if (axiosError.response?.status === 404) {
278
+ throw new OpenLoyaltyError({
279
+ code: "NOT_FOUND",
280
+ message: `Tier set '${input.tierSetId}' not found`,
281
+ hint: "Use ol_tierset_list() to find existing tier sets and their IDs.",
282
+ relatedTool: "ol_tierset_get_tiers",
283
+ });
284
+ }
194
285
  throw formatApiError(error, "ol_tierset_get_tiers");
195
286
  }
196
287
  }
@@ -7,6 +7,7 @@ export declare const tiersetToolDefinitions: readonly [{
7
7
  readonly title: "List Loyalty Programs";
8
8
  readonly description: string;
9
9
  readonly readOnly: true;
10
+ readonly idempotent: true;
10
11
  readonly inputSchema: {
11
12
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
12
13
  page: import("zod").ZodOptional<import("zod").ZodNumber>;
@@ -18,6 +19,7 @@ export declare const tiersetToolDefinitions: readonly [{
18
19
  readonly title: "Create Loyalty Program";
19
20
  readonly description: string;
20
21
  readonly readOnly: false;
22
+ readonly idempotent: false;
21
23
  readonly inputSchema: {
22
24
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
23
25
  name: import("zod").ZodString;
@@ -39,6 +41,7 @@ export declare const tiersetToolDefinitions: readonly [{
39
41
  readonly title: "Get Loyalty Program Details";
40
42
  readonly description: string;
41
43
  readonly readOnly: true;
44
+ readonly idempotent: true;
42
45
  readonly inputSchema: {
43
46
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
44
47
  tierSetId: import("zod").ZodString;
@@ -47,6 +50,7 @@ export declare const tiersetToolDefinitions: readonly [{
47
50
  }, {
48
51
  readonly name: "ol_tierset_update";
49
52
  readonly title: "Update Loyalty Program";
53
+ readonly idempotent: true;
50
54
  readonly description: string;
51
55
  readonly readOnly: false;
52
56
  readonly inputSchema: {
@@ -70,6 +74,7 @@ export declare const tiersetToolDefinitions: readonly [{
70
74
  }, {
71
75
  readonly name: "ol_tierset_update_tiers";
72
76
  readonly title: "Configure Tier Thresholds";
77
+ readonly idempotent: true;
73
78
  readonly description: "Add ALL tiers to a tier set in ONE call. A tier set is a CONTAINER - add all tiers (Bronze, Silver, Gold, etc.) to it together.\n\n⚠️ CRITICAL: Pass ALL tiers in a SINGLE call to this endpoint. DO NOT call this multiple times with one tier each.\n\n⚠️ INPUT FORMAT: Pass 'name' and 'description' as TOP-LEVEL fields on each tier object.\n❌ WRONG: { translations: { en: { name: \"Bronze\" } } }\n✅ CORRECT: { name: \"Bronze\", description: \"Entry tier\" }\n\nExample - Creating 4 tiers in ONE call:\ntiers: [\n { name: \"Bronze\", description: \"Entry tier\", conditions: [{ conditionId: \"xxx\", value: 0 }] },\n { name: \"Silver\", description: \"500+ points\", conditions: [{ conditionId: \"xxx\", value: 500 }] },\n { name: \"Gold\", description: \"1000+ points\", conditions: [{ conditionId: \"xxx\", value: 1000 }] },\n { name: \"Platinum\", description: \"Top tier\", conditions: [{ conditionId: \"xxx\", value: 2000 }] }\n]\n\nThe conditionId must match the one from tierset_get response - use the SAME conditionId for all tiers.";
74
79
  readonly readOnly: false;
75
80
  readonly inputSchema: {
@@ -116,6 +121,7 @@ export declare const tiersetToolDefinitions: readonly [{
116
121
  readonly title: "Get Tier Configuration";
117
122
  readonly description: "Get all tiers in a tier set. Returns levelId values that can be used for campaign targeting. Includes each tier's name and condition thresholds.";
118
123
  readonly readOnly: true;
124
+ readonly idempotent: true;
119
125
  readonly inputSchema: {
120
126
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
121
127
  tierSetId: import("zod").ZodString;
@@ -14,6 +14,7 @@ export const tiersetToolDefinitions = [
14
14
  "⚠️ ALWAYS check existing tier sets FIRST before creating - reuse if one exists. " +
15
15
  "Returns tierSetId, name, active status, and tier count for each tier set.",
16
16
  readOnly: true,
17
+ idempotent: true,
17
18
  inputSchema: TierSetListInputSchema,
18
19
  handler: tiersetList,
19
20
  },
@@ -28,6 +29,7 @@ export const tiersetToolDefinitions = [
28
29
  "REQUIRED: name, conditions array. Only name/description/conditions accepted at creation. " +
29
30
  "NOTE: Use 'attribute' NOT 'type' in conditions. For unit-based attributes, set walletType to 'default'.",
30
31
  readOnly: false,
32
+ idempotent: false,
31
33
  inputSchema: TierSetCreateInputSchema,
32
34
  handler: tiersetCreate,
33
35
  },
@@ -38,17 +40,20 @@ export const tiersetToolDefinitions = [
38
40
  "CRITICAL: After tierset_create, call this to get the conditionId from conditions[].id. " +
39
41
  "Use this SAME conditionId for ALL tiers when calling tierset_update_tiers.",
40
42
  readOnly: true,
43
+ idempotent: true,
41
44
  inputSchema: TierSetGetInputSchema,
42
45
  handler: tiersetGet,
43
46
  },
44
47
  {
45
48
  name: "ol_tierset_update",
46
49
  title: "Update Loyalty Program",
50
+ idempotent: true,
47
51
  description: "Update tier set metadata (name, description, active status, downgrade settings). " +
48
52
  "KEY DECISIONS (confirm with user if not specified): " +
49
53
  "1. Downgrade policy -- 'none' (members never lose tier), 'automatic' (instant downgrade), 'x_days' (grace period) " +
50
54
  "2. Active toggle -- activating affects ALL members' tier placement immediately " +
51
- "Automatically preserves existing conditions. Does not modify tiers -- use tierset_update_tiers for that.",
55
+ "Automatically preserves existing conditions. Does not modify tiers -- use tierset_update_tiers for that. " +
56
+ "downgrade.mode valid values: 'none' (no downgrade), 'x_days' (downgrade after X days -- set downgrade.days), 'automatic' (immediate downgrade when conditions not met).",
52
57
  readOnly: false,
53
58
  inputSchema: TierSetUpdateInputSchema,
54
59
  handler: tiersetUpdate,
@@ -56,6 +61,7 @@ export const tiersetToolDefinitions = [
56
61
  {
57
62
  name: "ol_tierset_update_tiers",
58
63
  title: "Configure Tier Thresholds",
64
+ idempotent: true,
59
65
  description: `Add ALL tiers to a tier set in ONE call. A tier set is a CONTAINER - add all tiers (Bronze, Silver, Gold, etc.) to it together.
60
66
 
61
67
  ⚠️ CRITICAL: Pass ALL tiers in a SINGLE call to this endpoint. DO NOT call this multiple times with one tier each.
@@ -82,6 +88,7 @@ The conditionId must match the one from tierset_get response - use the SAME cond
82
88
  title: "Get Tier Configuration",
83
89
  description: "Get all tiers in a tier set. Returns levelId values that can be used for campaign targeting. Includes each tier's name and condition thresholds.",
84
90
  readOnly: true,
91
+ idempotent: true,
85
92
  inputSchema: TierSetGetTiersInputSchema,
86
93
  handler: tiersetGetTiers,
87
94
  },
@@ -69,6 +69,15 @@ export async function transactionGet(input) {
69
69
  };
70
70
  }
71
71
  catch (error) {
72
+ const axiosError = error;
73
+ if (axiosError.response?.status === 404) {
74
+ throw new OpenLoyaltyError({
75
+ code: "NOT_FOUND",
76
+ message: `Transaction '${input.transactionId}' not found`,
77
+ hint: `Use ol_transaction_list(documentNumber: "...") to verify the transaction exists. The transactionId may be incorrect.`,
78
+ relatedTool: "ol_transaction_get",
79
+ });
80
+ }
72
81
  throw formatApiError(error, "ol_transaction_get");
73
82
  }
74
83
  }
@@ -154,6 +163,37 @@ export async function transactionAssignMember(input) {
154
163
  };
155
164
  }
156
165
  catch (error) {
166
+ const axiosError = error;
167
+ const apiErrors = axiosError.response?.data?.errors || [];
168
+ const allMessages = [
169
+ error instanceof Error ? error.message : "",
170
+ axiosError.response?.data?.message || "",
171
+ ...apiErrors.map(e => e.message)
172
+ ].join(" ").toLowerCase();
173
+ if (axiosError.response?.status === 404 || (allMessages.includes("transaction") && allMessages.includes("not found"))) {
174
+ throw new OpenLoyaltyError({
175
+ code: "TRANSACTION_NOT_FOUND",
176
+ message: `Transaction with document number '${input.documentNumber}' not found`,
177
+ hint: `Use ol_transaction_list(documentNumber: "${input.documentNumber}") to verify the transaction exists. The documentNumber must match exactly.`,
178
+ relatedTool: "ol_transaction_assign_member",
179
+ });
180
+ }
181
+ if (allMessages.includes("already") && (allMessages.includes("assigned") || allMessages.includes("matched"))) {
182
+ throw new OpenLoyaltyError({
183
+ code: "ALREADY_ASSIGNED",
184
+ message: `Transaction '${input.documentNumber}' is already assigned to a member`,
185
+ hint: `Use ol_transaction_list(documentNumber: "${input.documentNumber}") to see which member this transaction is assigned to. Transactions can only be assigned once.`,
186
+ relatedTool: "ol_transaction_assign_member",
187
+ });
188
+ }
189
+ if (allMessages.includes("customer") && allMessages.includes("not found")) {
190
+ throw new OpenLoyaltyError({
191
+ code: "MEMBER_NOT_FOUND",
192
+ message: "The specified member was not found",
193
+ hint: "Use ol_member_list() to search for the member by email, name, or phone. Verify the customerId, loyaltyCardNumber, or phone is correct.",
194
+ relatedTool: "ol_transaction_assign_member",
195
+ });
196
+ }
157
197
  throw formatApiError(error, "ol_transaction_assign_member");
158
198
  }
159
199
  }
@@ -6,6 +6,7 @@ export declare const transactionToolDefinitions: readonly [{
6
6
  readonly title: "Record Purchase";
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
  header: import("zod").ZodObject<{
@@ -114,6 +115,7 @@ export declare const transactionToolDefinitions: readonly [{
114
115
  readonly title: "Get Transaction Details";
115
116
  readonly description: "Get transaction details including items, customerData, matched status, and pointsEarned.";
116
117
  readonly readOnly: true;
118
+ readonly idempotent: true;
117
119
  readonly inputSchema: {
118
120
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
119
121
  transactionId: import("zod").ZodString;
@@ -122,6 +124,7 @@ export declare const transactionToolDefinitions: readonly [{
122
124
  }, {
123
125
  readonly name: "ol_transaction_list";
124
126
  readonly title: "Search Transactions";
127
+ readonly idempotent: true;
125
128
  readonly description: string;
126
129
  readonly readOnly: true;
127
130
  readonly inputSchema: {
@@ -142,6 +145,7 @@ export declare const transactionToolDefinitions: readonly [{
142
145
  readonly title: "Link Transaction to Member";
143
146
  readonly description: string;
144
147
  readonly readOnly: false;
148
+ readonly idempotent: false;
145
149
  readonly inputSchema: {
146
150
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
147
151
  documentNumber: import("zod").ZodString;
@@ -16,6 +16,7 @@ export const transactionToolDefinitions = [
16
16
  "NOTE: Use 'purchasedAt' NOT 'purchaseDate'. Pass sku as STRING, NOT object. " +
17
17
  "Returns transactionId and pointsEarned if campaigns triggered.",
18
18
  readOnly: false,
19
+ idempotent: false,
19
20
  inputSchema: TransactionCreateInputSchema,
20
21
  handler: transactionCreate,
21
22
  },
@@ -24,12 +25,14 @@ export const transactionToolDefinitions = [
24
25
  title: "Get Transaction Details",
25
26
  description: "Get transaction details including items, customerData, matched status, and pointsEarned.",
26
27
  readOnly: true,
28
+ idempotent: true,
27
29
  inputSchema: TransactionGetInputSchema,
28
30
  handler: transactionGet,
29
31
  },
30
32
  {
31
33
  name: "ol_transaction_list",
32
34
  title: "Search Transactions",
35
+ idempotent: true,
33
36
  description: "List transactions with optional filters. Filter by customerId, documentNumber, documentType, matched status, or date range (purchasedAtFrom/purchasedAtTo). " +
34
37
  "Use ISO date format for date filters (e.g., '2025-07-01' or '2025-07-01T00:00:00Z'). " +
35
38
  "Supports cursor pagination: provide 'cursor' from previous response to get next page. " +
@@ -48,6 +51,7 @@ export const transactionToolDefinitions = [
48
51
  description: "Assign unmatched transaction to member by ID, card number, or phone. Triggers point campaigns. " +
49
52
  "Use this to link a transaction that wasn't matched at creation time. Returns transactionId, customerId, and pointsEarned.",
50
53
  readOnly: false,
54
+ idempotent: false,
51
55
  inputSchema: TransactionAssignMemberInputSchema,
52
56
  handler: transactionAssignMember,
53
57
  },
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
2
  // Input Schemas
3
3
  const LabelInputSchema = z.object({
4
- key: z.string(),
5
- value: z.string(),
4
+ key: z.string().describe("Label key (e.g., 'category', 'channel')."),
5
+ value: z.string().describe("Label value (e.g., 'electronics', 'online')."),
6
6
  });
7
7
  const TransactionHeaderInputSchema = z.object({
8
8
  documentNumber: z.string().describe("Unique document number (required, unique per store)."),
@@ -15,7 +15,7 @@ const TransactionHeaderInputSchema = z.object({
15
15
  const TransactionItemInputSchema = z.object({
16
16
  sku: z.string().describe("Product SKU as a STRING (required). Use 'sku: \"PROD-123\"' NOT 'sku: { code: \"...\" }'."),
17
17
  name: z.string().describe("Product name (required, max 255 chars)."),
18
- grossValue: z.number().describe("Item gross value (required)."),
18
+ grossValue: z.number().describe("Item total gross value (required). This is the total for the line item (unit price * quantity)."),
19
19
  category: z.string().describe("Product category (required, max 255 chars)."),
20
20
  quantity: z.number().optional().describe("Item quantity (integer)."),
21
21
  highPrecisionQuantity: z.number().optional().describe("High precision quantity (up to 3 decimals, recommended)."),