@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,45 @@
1
+ import { BadgeTypeListItem, MemberBadge } from "../../types/schemas/badge.js";
2
+ export declare function badgeList(input: {
3
+ storeCode?: string;
4
+ page?: number;
5
+ perPage?: number;
6
+ name?: string;
7
+ badgeTypeId?: string;
8
+ }): Promise<{
9
+ badges: BadgeTypeListItem[];
10
+ total: {
11
+ all?: number;
12
+ filtered?: number;
13
+ };
14
+ }>;
15
+ export declare function badgeGet(input: {
16
+ storeCode?: string;
17
+ badgeTypeId: string;
18
+ }): Promise<Record<string, unknown>>;
19
+ interface BadgeUpdateInput {
20
+ storeCode?: string;
21
+ badgeTypeId: string;
22
+ name?: string;
23
+ translations?: Record<string, {
24
+ name: string;
25
+ description?: string;
26
+ }>;
27
+ imageUrl?: string;
28
+ active?: boolean;
29
+ }
30
+ export declare function badgeUpdate(input: BadgeUpdateInput): Promise<void>;
31
+ export declare function badgeGetMemberBadges(input: {
32
+ storeCode?: string;
33
+ memberId: string;
34
+ page?: number;
35
+ perPage?: number;
36
+ name?: string;
37
+ badgeTypeId?: string;
38
+ }): Promise<{
39
+ badges: MemberBadge[];
40
+ total: {
41
+ all?: number;
42
+ filtered?: number;
43
+ };
44
+ }>;
45
+ export {};
@@ -0,0 +1,135 @@
1
+ import { apiGet, apiPut } from "../../client/http.js";
2
+ import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
3
+ import axios from "axios";
4
+ import { getStoreCode } from "../../config.js";
5
+ export async function badgeList(input) {
6
+ const storeCode = getStoreCode(input.storeCode);
7
+ const params = new URLSearchParams();
8
+ if (input.page)
9
+ params.append("_page", String(input.page));
10
+ if (input.perPage)
11
+ params.append("_itemsOnPage", String(input.perPage));
12
+ if (input.name)
13
+ params.append("name", input.name);
14
+ if (input.badgeTypeId)
15
+ params.append("badgeTypeId", input.badgeTypeId);
16
+ const queryString = params.toString();
17
+ const url = `/${storeCode}/badge-type${queryString ? `?${queryString}` : ""}`;
18
+ try {
19
+ const response = await apiGet(url);
20
+ const badges = (response.items || []).map((item) => ({
21
+ badgeTypeId: item.badgeTypeId,
22
+ name: item.name,
23
+ createdAt: item.createdAt,
24
+ updatedAt: item.updatedAt,
25
+ }));
26
+ const total = response.total || {};
27
+ return {
28
+ badges,
29
+ total: {
30
+ all: typeof total.all === "number" ? total.all : undefined,
31
+ filtered: typeof total.filtered === "number" ? total.filtered : undefined,
32
+ },
33
+ };
34
+ }
35
+ catch (error) {
36
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
37
+ throw new OpenLoyaltyError({
38
+ code: "STORE_NOT_FOUND",
39
+ message: "Cannot list badges - store not found",
40
+ hint: "Use ol_store_list() to verify the store code is correct.",
41
+ relatedTool: "ol_badge_list",
42
+ });
43
+ }
44
+ if (axios.isAxiosError(error) && error.response?.status === 403) {
45
+ throw new OpenLoyaltyError({
46
+ code: "BADGE_PERMISSION_DENIED",
47
+ message: "You don't have permission to view badges",
48
+ hint: "Badge access requires the ACHIEVEMENT:VIEW permission. Use ol_admin_get_permissions() to check your access.",
49
+ relatedTool: "ol_badge_list",
50
+ });
51
+ }
52
+ throw formatApiError(error, "ol_badge_list");
53
+ }
54
+ }
55
+ export async function badgeGet(input) {
56
+ const storeCode = getStoreCode(input.storeCode);
57
+ try {
58
+ const response = await apiGet(`/${storeCode}/badge-type/${input.badgeTypeId}`);
59
+ return response;
60
+ }
61
+ catch (error) {
62
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
63
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Badge type '${input.badgeTypeId}' not found`,
64
+ hint: "Use ol_badge_list() to find existing badge types and their IDs.", relatedTool: "ol_badge_get" });
65
+ }
66
+ throw formatApiError(error, "ol_badge_get");
67
+ }
68
+ }
69
+ export async function badgeUpdate(input) {
70
+ const storeCode = getStoreCode(input.storeCode);
71
+ const badgePayload = {};
72
+ // API requires name as direct field, not nested in translations
73
+ // Extract from translations.en.name if name not provided directly
74
+ let badgeName = input.name;
75
+ if (!badgeName && input.translations?.en?.name) {
76
+ badgeName = input.translations.en.name;
77
+ }
78
+ if (badgeName !== undefined)
79
+ badgePayload.name = badgeName;
80
+ // Note: API does not accept nested translations - only direct fields
81
+ if (input.imageUrl !== undefined)
82
+ badgePayload.imageUrl = input.imageUrl;
83
+ if (input.active !== undefined)
84
+ badgePayload.active = input.active;
85
+ try {
86
+ // CRITICAL: Wrap body as { badgeType: {...} }
87
+ await apiPut(`/${storeCode}/badge-type/${input.badgeTypeId}`, { badgeType: badgePayload });
88
+ }
89
+ catch (error) {
90
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
91
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Badge type '${input.badgeTypeId}' not found`,
92
+ hint: "Use ol_badge_list() to find existing badge types and their IDs.", relatedTool: "ol_badge_update" });
93
+ }
94
+ throw formatApiError(error, "ol_badge_update");
95
+ }
96
+ }
97
+ export async function badgeGetMemberBadges(input) {
98
+ const storeCode = getStoreCode(input.storeCode);
99
+ const params = new URLSearchParams();
100
+ if (input.page)
101
+ params.append("_page", String(input.page));
102
+ if (input.perPage)
103
+ params.append("_itemsOnPage", String(input.perPage));
104
+ if (input.name)
105
+ params.append("name", input.name);
106
+ if (input.badgeTypeId)
107
+ params.append("badgeTypeId", input.badgeTypeId);
108
+ const queryString = params.toString();
109
+ const url = `/${storeCode}/member/${input.memberId}/badge${queryString ? `?${queryString}` : ""}`;
110
+ try {
111
+ const response = await apiGet(url);
112
+ const badges = (response.items || []).map((item) => ({
113
+ badgeTypeId: item.badgeTypeId,
114
+ name: item.name,
115
+ completedCount: item.completedCount,
116
+ createdAt: item.createdAt,
117
+ updatedAt: item.updatedAt,
118
+ }));
119
+ const total = response.total || {};
120
+ return {
121
+ badges,
122
+ total: {
123
+ all: typeof total.all === "number" ? total.all : undefined,
124
+ filtered: typeof total.filtered === "number" ? total.filtered : undefined,
125
+ },
126
+ };
127
+ }
128
+ catch (error) {
129
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
130
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Member '${input.memberId}' not found`,
131
+ hint: "Use ol_member_list() to search for the member by email, name, or phone.", relatedTool: "ol_badge_get_member_badges" });
132
+ }
133
+ throw formatApiError(error, "ol_badge_get_member_badges");
134
+ }
135
+ }
@@ -0,0 +1,68 @@
1
+ export { BadgeListInputSchema, BadgeGetInputSchema, BadgeUpdateInputSchema, BadgeGetMemberBadgesInputSchema, } from "./schemas.js";
2
+ export { badgeList, badgeGet, badgeUpdate, badgeGetMemberBadges, } from "./handlers.js";
3
+ import { badgeList, badgeGet, badgeUpdate, badgeGetMemberBadges } from "./handlers.js";
4
+ export declare const badgeToolDefinitions: readonly [{
5
+ readonly name: "ol_badge_list";
6
+ readonly title: "List Badges";
7
+ readonly description: "List badge types. Badges are visual rewards linked to achievements. When a member completes an achievement with a badgeTypeId, they earn that badge. Use for displaying available badges.";
8
+ readonly readOnly: true;
9
+ readonly idempotent: true;
10
+ readonly inputSchema: {
11
+ storeCode: import("zod").ZodOptional<import("zod").ZodString>;
12
+ page: import("zod").ZodOptional<import("zod").ZodNumber>;
13
+ perPage: import("zod").ZodOptional<import("zod").ZodNumber>;
14
+ name: import("zod").ZodOptional<import("zod").ZodString>;
15
+ badgeTypeId: import("zod").ZodOptional<import("zod").ZodString>;
16
+ };
17
+ readonly handler: typeof badgeList;
18
+ }, {
19
+ readonly name: "ol_badge_get";
20
+ readonly title: "Get Badge Details";
21
+ readonly description: "Get badge type details including name, image URL, and linked achievements count.";
22
+ readonly readOnly: true;
23
+ readonly idempotent: true;
24
+ readonly inputSchema: {
25
+ storeCode: import("zod").ZodOptional<import("zod").ZodString>;
26
+ badgeTypeId: import("zod").ZodString;
27
+ };
28
+ readonly handler: typeof badgeGet;
29
+ }, {
30
+ readonly name: "ol_badge_update";
31
+ readonly title: "Update Badge";
32
+ readonly description: string;
33
+ readonly readOnly: false;
34
+ readonly idempotent: true;
35
+ readonly inputSchema: {
36
+ storeCode: import("zod").ZodOptional<import("zod").ZodString>;
37
+ badgeTypeId: import("zod").ZodString;
38
+ name: import("zod").ZodString;
39
+ translations: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
40
+ name: import("zod").ZodString;
41
+ description: import("zod").ZodOptional<import("zod").ZodString>;
42
+ }, "strip", import("zod").ZodTypeAny, {
43
+ name: string;
44
+ description?: string | undefined;
45
+ }, {
46
+ name: string;
47
+ description?: string | undefined;
48
+ }>>>;
49
+ imageUrl: import("zod").ZodOptional<import("zod").ZodString>;
50
+ active: import("zod").ZodOptional<import("zod").ZodBoolean>;
51
+ };
52
+ readonly handler: typeof badgeUpdate;
53
+ }, {
54
+ readonly name: "ol_badge_get_member_badges";
55
+ readonly title: "Get Member Badges";
56
+ readonly description: "Get badges earned by a member. Returns each badge with completedCount showing how many times it was earned. Use for displaying member's badge collection.";
57
+ readonly readOnly: true;
58
+ readonly idempotent: true;
59
+ readonly inputSchema: {
60
+ storeCode: import("zod").ZodOptional<import("zod").ZodString>;
61
+ memberId: import("zod").ZodString;
62
+ page: import("zod").ZodOptional<import("zod").ZodNumber>;
63
+ perPage: import("zod").ZodOptional<import("zod").ZodNumber>;
64
+ name: import("zod").ZodOptional<import("zod").ZodString>;
65
+ badgeTypeId: import("zod").ZodOptional<import("zod").ZodString>;
66
+ };
67
+ readonly handler: typeof badgeGetMemberBadges;
68
+ }];
@@ -0,0 +1,47 @@
1
+ // Re-export schemas
2
+ export { BadgeListInputSchema, BadgeGetInputSchema, BadgeUpdateInputSchema, BadgeGetMemberBadgesInputSchema, } from "./schemas.js";
3
+ // Re-export handlers
4
+ export { badgeList, badgeGet, badgeUpdate, badgeGetMemberBadges, } from "./handlers.js";
5
+ // Imports for tool definitions
6
+ import { BadgeListInputSchema, BadgeGetInputSchema, BadgeUpdateInputSchema, BadgeGetMemberBadgesInputSchema, } from "./schemas.js";
7
+ import { badgeList, badgeGet, badgeUpdate, badgeGetMemberBadges, } from "./handlers.js";
8
+ // Tool definitions
9
+ export const badgeToolDefinitions = [
10
+ {
11
+ name: "ol_badge_list",
12
+ title: "List Badges",
13
+ description: "List badge types. Badges are visual rewards linked to achievements. When a member completes an achievement with a badgeTypeId, they earn that badge. Use for displaying available badges.",
14
+ readOnly: true,
15
+ idempotent: true,
16
+ inputSchema: BadgeListInputSchema,
17
+ handler: badgeList,
18
+ },
19
+ {
20
+ name: "ol_badge_get",
21
+ title: "Get Badge Details",
22
+ description: "Get badge type details including name, image URL, and linked achievements count.",
23
+ readOnly: true,
24
+ idempotent: true,
25
+ inputSchema: BadgeGetInputSchema,
26
+ handler: badgeGet,
27
+ },
28
+ {
29
+ name: "ol_badge_update",
30
+ title: "Update Badge",
31
+ description: "Update badge type configuration. Badge types are created automatically when referenced by achievements. " +
32
+ "FIELD FORMAT: Pass 'name' as a direct top-level string, NOT inside translations. The API requires { name: 'Badge Name' }, NOT { translations: { en: { name: '...' } } }.",
33
+ readOnly: false,
34
+ idempotent: true,
35
+ inputSchema: BadgeUpdateInputSchema,
36
+ handler: badgeUpdate,
37
+ },
38
+ {
39
+ name: "ol_badge_get_member_badges",
40
+ title: "Get Member Badges",
41
+ description: "Get badges earned by a member. Returns each badge with completedCount showing how many times it was earned. Use for displaying member's badge collection.",
42
+ readOnly: true,
43
+ idempotent: true,
44
+ inputSchema: BadgeGetMemberBadgesInputSchema,
45
+ handler: badgeGetMemberBadges,
46
+ },
47
+ ];
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ export declare const BadgeListInputSchema: {
3
+ storeCode: z.ZodOptional<z.ZodString>;
4
+ page: z.ZodOptional<z.ZodNumber>;
5
+ perPage: z.ZodOptional<z.ZodNumber>;
6
+ name: z.ZodOptional<z.ZodString>;
7
+ badgeTypeId: z.ZodOptional<z.ZodString>;
8
+ };
9
+ export declare const BadgeGetInputSchema: {
10
+ storeCode: z.ZodOptional<z.ZodString>;
11
+ badgeTypeId: z.ZodString;
12
+ };
13
+ export declare const BadgeUpdateInputSchema: {
14
+ storeCode: z.ZodOptional<z.ZodString>;
15
+ badgeTypeId: z.ZodString;
16
+ name: z.ZodString;
17
+ translations: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
18
+ name: z.ZodString;
19
+ description: z.ZodOptional<z.ZodString>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ name: string;
22
+ description?: string | undefined;
23
+ }, {
24
+ name: string;
25
+ description?: string | undefined;
26
+ }>>>;
27
+ imageUrl: z.ZodOptional<z.ZodString>;
28
+ active: z.ZodOptional<z.ZodBoolean>;
29
+ };
30
+ export declare const BadgeGetMemberBadgesInputSchema: {
31
+ storeCode: z.ZodOptional<z.ZodString>;
32
+ memberId: z.ZodString;
33
+ page: z.ZodOptional<z.ZodNumber>;
34
+ perPage: z.ZodOptional<z.ZodNumber>;
35
+ name: z.ZodOptional<z.ZodString>;
36
+ badgeTypeId: z.ZodOptional<z.ZodString>;
37
+ };
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ export const BadgeListInputSchema = {
3
+ 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."),
4
+ page: z.number().optional().describe("Page number (default: 1)."),
5
+ perPage: z.number().optional().describe("Items per page (default: 10)."),
6
+ name: z.string().optional().describe("Filter by badge name."),
7
+ badgeTypeId: z.string().optional().describe("Filter by specific badge type ID."),
8
+ };
9
+ export const BadgeGetInputSchema = {
10
+ 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."),
11
+ badgeTypeId: z.string().describe("The badge type ID (UUID) to retrieve."),
12
+ };
13
+ export const BadgeUpdateInputSchema = {
14
+ 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."),
15
+ badgeTypeId: z.string().describe("The badge type ID (UUID) to update."),
16
+ name: z.string().describe("Badge name (REQUIRED). API requires name as a direct field."),
17
+ translations: z.record(z.string(), z.object({
18
+ name: z.string(),
19
+ description: z.string().optional(),
20
+ })).optional().describe("Badge translations (for convenience - will extract name from translations.en.name if name not provided directly)."),
21
+ imageUrl: z.string().optional().describe("URL to badge image."),
22
+ active: z.boolean().optional().describe("Whether badge type is active."),
23
+ };
24
+ export const BadgeGetMemberBadgesInputSchema = {
25
+ 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."),
26
+ memberId: z.string().describe("The member ID (UUID)."),
27
+ page: z.number().optional().describe("Page number (default: 1)."),
28
+ perPage: z.number().optional().describe("Items per page (default: 10)."),
29
+ name: z.string().optional().describe("Filter by badge name."),
30
+ badgeTypeId: z.string().optional().describe("Filter by specific badge type ID."),
31
+ };
@@ -62,6 +62,15 @@ export async function campaignGet(input) {
62
62
  return response;
63
63
  }
64
64
  catch (error) {
65
+ const axiosError = error;
66
+ if (axiosError.response?.status === 404) {
67
+ throw new OpenLoyaltyError({
68
+ code: "NOT_FOUND",
69
+ message: `Campaign '${input.campaignId}' not found`,
70
+ hint: "Use ol_campaign_list() to find existing campaigns and their IDs.",
71
+ relatedTool: "ol_campaign_get",
72
+ });
73
+ }
65
74
  throw formatApiError(error, "ol_campaign_get");
66
75
  }
67
76
  }
@@ -140,6 +149,23 @@ export async function campaignUpdate(input) {
140
149
  await apiPut(`/${storeCode}/campaign/${campaignId}`, { campaign: campaignPayload });
141
150
  }
142
151
  catch (error) {
152
+ const axiosError = error;
153
+ if (axiosError.response?.status === 404) {
154
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Campaign '${campaignId}' not found`,
155
+ hint: "Use ol_campaign_list() to find existing campaigns and their IDs.", relatedTool: "ol_campaign_update" });
156
+ }
157
+ const apiErrors = axiosError.response?.data?.errors || [];
158
+ const allMessages = [error instanceof Error ? error.message : "", axiosError.response?.data?.message || "",
159
+ ...apiErrors.map(e => `${e.path || ""}: ${e.message}`)].join(" ").toLowerCase();
160
+ if (allMessages.includes("trigger") && allMessages.includes("choice")) {
161
+ throw new OpenLoyaltyError({ code: "INVALID_TRIGGER", message: "Invalid campaign trigger type",
162
+ hint: "Valid trigger values: 'transaction', 'custom_event', 'geolocation', 'time', 'achievement'.", relatedTool: "ol_campaign_update" });
163
+ }
164
+ if (allMessages.includes("operator") || (allMessages.includes("condition") && allMessages.includes("choice"))) {
165
+ throw new OpenLoyaltyError({ code: "INVALID_CONDITION", message: "Invalid rule condition in campaign",
166
+ hint: "Use full operator names: 'is_greater_or_equal' (NOT 'gte'). Use full attribute paths: 'transaction.grossValue' (NOT 'grossValue').",
167
+ relatedTool: "ol_campaign_update" });
168
+ }
143
169
  throw formatApiError(error, "ol_campaign_update");
144
170
  }
145
171
  }
@@ -154,6 +180,11 @@ export async function campaignPatch(input) {
154
180
  await apiPatch(`/${storeCode}/campaign/${input.campaignId}`, { campaign: patchPayload });
155
181
  }
156
182
  catch (error) {
183
+ const axiosError = error;
184
+ if (axiosError.response?.status === 404) {
185
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Campaign '${input.campaignId}' not found`,
186
+ hint: "Use ol_campaign_list() to find existing campaigns and their IDs.", relatedTool: "ol_campaign_patch" });
187
+ }
157
188
  throw formatApiError(error, "ol_campaign_patch");
158
189
  }
159
190
  }
@@ -163,6 +194,11 @@ export async function campaignDelete(input) {
163
194
  await apiDelete(`/${storeCode}/campaign/${input.campaignId}`);
164
195
  }
165
196
  catch (error) {
197
+ const axiosError = error;
198
+ if (axiosError.response?.status === 404) {
199
+ throw new OpenLoyaltyError({ code: "NOT_FOUND", message: `Campaign '${input.campaignId}' not found`,
200
+ hint: "Use ol_campaign_list() to find existing campaigns and their IDs.", relatedTool: "ol_campaign_delete" });
201
+ }
166
202
  throw formatApiError(error, "ol_campaign_delete");
167
203
  }
168
204
  }
@@ -252,6 +288,31 @@ export async function campaignSimulate(input) {
252
288
  };
253
289
  }
254
290
  catch (error) {
291
+ const axiosError = error;
292
+ const apiErrors = axiosError.response?.data?.errors || [];
293
+ const allMessages = [
294
+ error instanceof Error ? error.message : "",
295
+ axiosError.response?.data?.message || "",
296
+ ...apiErrors.map(e => `${e.path || ""}: ${e.message}`)
297
+ ].join(" ").toLowerCase();
298
+ if (allMessages.includes("transaction") && (allMessages.includes("required") || allMessages.includes("missing") || allMessages.includes("should not be blank"))) {
299
+ throw new OpenLoyaltyError({
300
+ code: "MISSING_TRANSACTION_DATA",
301
+ message: "Transaction data is required for simulation with transaction trigger",
302
+ hint: "For trigger='transaction', provide transaction data: { grossValue: 100, documentNumber: 'SIM-001', " +
303
+ "purchasedAt: '2026-01-01T10:00:00Z', items: [{ sku: 'SKU-1', name: 'Product', grossValue: 100, qty: 1 }] }. " +
304
+ "Also provide customer: { customerId: 'member-uuid' } to identify the member.",
305
+ relatedTool: "ol_campaign_simulate",
306
+ });
307
+ }
308
+ if (allMessages.includes("customer") && (allMessages.includes("not found") || allMessages.includes("required"))) {
309
+ throw new OpenLoyaltyError({
310
+ code: "MEMBER_NOT_FOUND",
311
+ message: "The specified member was not found for simulation",
312
+ hint: "Provide a valid member ID in customer.customerId. Use ol_member_list() to find members.",
313
+ relatedTool: "ol_campaign_simulate",
314
+ });
315
+ }
255
316
  throw formatApiError(error, "ol_campaign_simulate");
256
317
  }
257
318
  }
@@ -9,6 +9,7 @@ export declare const campaignToolDefinitions: readonly [{
9
9
  readonly title: "List Campaigns";
10
10
  readonly description: "List all campaigns. Filter by type (direct/referral) or trigger (transaction/custom_event/time/etc). Use campaign_get for full configuration including rules and effects.";
11
11
  readonly readOnly: true;
12
+ readonly idempotent: true;
12
13
  readonly inputSchema: {
13
14
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
14
15
  page: import("zod").ZodOptional<import("zod").ZodNumber>;
@@ -23,6 +24,7 @@ export declare const campaignToolDefinitions: readonly [{
23
24
  readonly title: "Create Campaign";
24
25
  readonly description: string;
25
26
  readonly readOnly: false;
27
+ readonly idempotent: false;
26
28
  readonly inputSchema: {
27
29
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
28
30
  type: import("zod").ZodEnum<["direct", "referral"]>;
@@ -313,6 +315,7 @@ export declare const campaignToolDefinitions: readonly [{
313
315
  readonly title: "Get Campaign Details";
314
316
  readonly description: "Get full campaign configuration including all rules, conditions, effects, and targeting.";
315
317
  readonly readOnly: true;
318
+ readonly idempotent: true;
316
319
  readonly inputSchema: {
317
320
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
318
321
  campaignId: import("zod").ZodString;
@@ -323,6 +326,7 @@ export declare const campaignToolDefinitions: readonly [{
323
326
  readonly title: "Update Campaign";
324
327
  readonly description: "Full update of campaign configuration. Requires all campaign fields. Use campaign_get first to retrieve current configuration, modify it, then send the complete object.";
325
328
  readonly readOnly: false;
329
+ readonly idempotent: true;
326
330
  readonly inputSchema: {
327
331
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
328
332
  campaignId: import("zod").ZodString;
@@ -603,6 +607,7 @@ export declare const campaignToolDefinitions: readonly [{
603
607
  readonly title: "Patch Campaign";
604
608
  readonly description: "Partial update of campaign - only active status and displayOrder can be patched. For full updates, use campaign_update.";
605
609
  readonly readOnly: false;
610
+ readonly idempotent: true;
606
611
  readonly inputSchema: {
607
612
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
608
613
  campaignId: import("zod").ZodString;
@@ -616,6 +621,7 @@ export declare const campaignToolDefinitions: readonly [{
616
621
  readonly description: "Permanently delete a campaign. This cannot be undone. Consider deactivating instead if you may need the campaign later.";
617
622
  readonly readOnly: false;
618
623
  readonly destructive: true;
624
+ readonly idempotent: true;
619
625
  readonly inputSchema: {
620
626
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
621
627
  campaignId: import("zod").ZodString;
@@ -624,6 +630,7 @@ export declare const campaignToolDefinitions: readonly [{
624
630
  }, {
625
631
  readonly name: "ol_campaign_simulate";
626
632
  readonly title: "Simulate Campaign Effects";
633
+ readonly idempotent: true;
627
634
  readonly description: string;
628
635
  readonly readOnly: true;
629
636
  readonly inputSchema: {
@@ -785,6 +792,7 @@ export declare const campaignToolDefinitions: readonly [{
785
792
  readonly title: "Generate Campaign Codes";
786
793
  readonly description: "Generate unique redemption codes for a campaign. Use for promotions requiring unique codes. Codes are auto-generated and can be retrieved with campaign_list_codes.";
787
794
  readonly readOnly: false;
795
+ readonly idempotent: false;
788
796
  readonly inputSchema: {
789
797
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
790
798
  campaignId: import("zod").ZodString;
@@ -796,6 +804,7 @@ export declare const campaignToolDefinitions: readonly [{
796
804
  readonly title: "List Campaign Codes";
797
805
  readonly description: "List redemption codes for a campaign. Filter by status (active/used) or specific code. Returns code details including usage status.";
798
806
  readonly readOnly: true;
807
+ readonly idempotent: true;
799
808
  readonly inputSchema: {
800
809
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
801
810
  campaignId: import("zod").ZodString;
@@ -810,6 +819,7 @@ export declare const campaignToolDefinitions: readonly [{
810
819
  readonly title: "Get Available Campaigns for Member";
811
820
  readonly description: "Get campaigns available to a specific member. Returns campaigns the member can participate in based on their tier, segments, and campaign targeting rules. Includes limitReached indicator.";
812
821
  readonly readOnly: true;
822
+ readonly idempotent: true;
813
823
  readonly inputSchema: {
814
824
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
815
825
  memberId: import("zod").ZodString;
@@ -824,6 +834,7 @@ export declare const campaignToolDefinitions: readonly [{
824
834
  readonly title: "Get Visible Campaigns for Member";
825
835
  readonly description: "Get campaigns visible to a specific member. May include campaigns not yet available (e.g., upcoming campaigns or those requiring certain conditions). Use get_available for campaigns the member can currently participate in.";
826
836
  readonly readOnly: true;
837
+ readonly idempotent: true;
827
838
  readonly inputSchema: {
828
839
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
829
840
  memberId: import("zod").ZodString;
@@ -837,6 +848,7 @@ export declare const campaignToolDefinitions: readonly [{
837
848
  readonly title: "Get Campaign Leaderboard";
838
849
  readonly description: "Get leaderboard rankings for a campaign. Returns ranked list of members with scores and positions. Use for leaderboard-type campaigns to show competition standings.";
839
850
  readonly readOnly: true;
851
+ readonly idempotent: true;
840
852
  readonly inputSchema: {
841
853
  storeCode: import("zod").ZodOptional<import("zod").ZodString>;
842
854
  campaignId: import("zod").ZodString;
@@ -14,6 +14,7 @@ export const campaignToolDefinitions = [
14
14
  title: "List Campaigns",
15
15
  description: "List all campaigns. Filter by type (direct/referral) or trigger (transaction/custom_event/time/etc). Use campaign_get for full configuration including rules and effects.",
16
16
  readOnly: true,
17
+ idempotent: true,
17
18
  inputSchema: CampaignListInputSchema,
18
19
  handler: campaignList,
19
20
  },
@@ -27,8 +28,16 @@ export const campaignToolDefinitions = [
27
28
  "3. Audience -- all members, or specific tiers/segments? " +
28
29
  "4. End date -- campaigns without endsAt run forever, which is a budget commitment. " +
29
30
  "REQUIRED: type, trigger, translations.en.name, activity.startsAt, rules with effects. " +
30
- "In effects array, use key 'effect' (NOT 'type'). pointsRule is a STRING: '100' not 100.",
31
+ "For trigger='custom_event': ALSO set event='your_event_code' (must match existing schema from custom_event_schema_list). " +
32
+ "EFFECT KEY: Use 'effect' NOT 'type' in effects array: { effect: 'give_points', pointsRule: '100' }. pointsRule MUST be a STRING: '100' not 100. " +
33
+ "RULE CONDITIONS EXAMPLES: " +
34
+ "{ conditions: [{ operator: 'is_greater_or_equal', attribute: 'transaction.grossValue', data: 100 }] }. " +
35
+ "For labels: { operator: 'has_at_least_one_label', attribute: 'transaction.labels', data: [{key: 'category', value: 'electronics'}] }. " +
36
+ "For between: { operator: 'is_between', attribute: 'transaction.grossValue', data: {from: 50, to: 200} }. " +
37
+ "AUDIENCE: OMIT entirely for all members. Do NOT pass audience: { target: 'all' } -- the API rejects 'all'. Only use audience for tier/segment targeting. " +
38
+ "VISIBILITY vs AUDIENCE: visibility = who can see the campaign. audience = who can participate. Both accept target='tier' or 'segment'. OMIT audience for all members.",
31
39
  readOnly: false,
40
+ idempotent: false,
32
41
  inputSchema: CampaignCreateInputSchema,
33
42
  handler: campaignCreate,
34
43
  },
@@ -37,6 +46,7 @@ export const campaignToolDefinitions = [
37
46
  title: "Get Campaign Details",
38
47
  description: "Get full campaign configuration including all rules, conditions, effects, and targeting.",
39
48
  readOnly: true,
49
+ idempotent: true,
40
50
  inputSchema: CampaignGetInputSchema,
41
51
  handler: campaignGet,
42
52
  },
@@ -45,6 +55,7 @@ export const campaignToolDefinitions = [
45
55
  title: "Update Campaign",
46
56
  description: "Full update of campaign configuration. Requires all campaign fields. Use campaign_get first to retrieve current configuration, modify it, then send the complete object.",
47
57
  readOnly: false,
58
+ idempotent: true,
48
59
  inputSchema: CampaignUpdateInputSchema,
49
60
  handler: campaignUpdate,
50
61
  },
@@ -53,6 +64,7 @@ export const campaignToolDefinitions = [
53
64
  title: "Patch Campaign",
54
65
  description: "Partial update of campaign - only active status and displayOrder can be patched. For full updates, use campaign_update.",
55
66
  readOnly: false,
67
+ idempotent: true,
56
68
  inputSchema: CampaignPatchInputSchema,
57
69
  handler: campaignPatch,
58
70
  },
@@ -62,12 +74,14 @@ export const campaignToolDefinitions = [
62
74
  description: "Permanently delete a campaign. This cannot be undone. Consider deactivating instead if you may need the campaign later.",
63
75
  readOnly: false,
64
76
  destructive: true,
77
+ idempotent: true,
65
78
  inputSchema: CampaignDeleteInputSchema,
66
79
  handler: campaignDelete,
67
80
  },
68
81
  {
69
82
  name: "ol_campaign_simulate",
70
83
  title: "Simulate Campaign Effects",
84
+ idempotent: true,
71
85
  description: "Simulate campaign effects without executing them. Use to preview what points/rewards a transaction would earn. " +
72
86
  "Example for transaction trigger: { trigger: 'transaction', " +
73
87
  "transaction: { grossValue: 100, documentNumber: 'SIM-001', purchasedAt: '2026-01-01T10:00:00Z', " +
@@ -83,6 +97,7 @@ export const campaignToolDefinitions = [
83
97
  title: "Generate Campaign Codes",
84
98
  description: "Generate unique redemption codes for a campaign. Use for promotions requiring unique codes. Codes are auto-generated and can be retrieved with campaign_list_codes.",
85
99
  readOnly: false,
100
+ idempotent: false,
86
101
  inputSchema: CampaignGenerateCodesInputSchema,
87
102
  handler: campaignGenerateCodes,
88
103
  },
@@ -91,6 +106,7 @@ export const campaignToolDefinitions = [
91
106
  title: "List Campaign Codes",
92
107
  description: "List redemption codes for a campaign. Filter by status (active/used) or specific code. Returns code details including usage status.",
93
108
  readOnly: true,
109
+ idempotent: true,
94
110
  inputSchema: CampaignListCodesInputSchema,
95
111
  handler: campaignListCodes,
96
112
  },
@@ -99,6 +115,7 @@ export const campaignToolDefinitions = [
99
115
  title: "Get Available Campaigns for Member",
100
116
  description: "Get campaigns available to a specific member. Returns campaigns the member can participate in based on their tier, segments, and campaign targeting rules. Includes limitReached indicator.",
101
117
  readOnly: true,
118
+ idempotent: true,
102
119
  inputSchema: CampaignGetAvailableInputSchema,
103
120
  handler: campaignGetAvailable,
104
121
  },
@@ -107,6 +124,7 @@ export const campaignToolDefinitions = [
107
124
  title: "Get Visible Campaigns for Member",
108
125
  description: "Get campaigns visible to a specific member. May include campaigns not yet available (e.g., upcoming campaigns or those requiring certain conditions). Use get_available for campaigns the member can currently participate in.",
109
126
  readOnly: true,
127
+ idempotent: true,
110
128
  inputSchema: CampaignGetVisibleInputSchema,
111
129
  handler: campaignGetVisible,
112
130
  },
@@ -115,6 +133,7 @@ export const campaignToolDefinitions = [
115
133
  title: "Get Campaign Leaderboard",
116
134
  description: "Get leaderboard rankings for a campaign. Returns ranked list of members with scores and positions. Use for leaderboard-type campaigns to show competition standings.",
117
135
  readOnly: true,
136
+ idempotent: true,
118
137
  inputSchema: CampaignGetLeaderboardInputSchema,
119
138
  handler: campaignGetLeaderboard,
120
139
  },