@open-loyalty/mcp-server 1.1.0 → 1.3.3
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.
- package/README.md +180 -177
- package/dist/auth/provider.js +2 -14
- package/dist/auth/storage.js +22 -0
- package/dist/client/http.js +10 -0
- package/dist/config.d.ts +0 -13
- package/dist/config.js +0 -14
- package/dist/http.js +35 -3
- package/dist/instructions.d.ts +5 -0
- package/dist/instructions.js +440 -0
- package/dist/prompts/fan-engagement-setup.d.ts +107 -0
- package/dist/prompts/fan-engagement-setup.js +492 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +60 -273
- package/dist/tools/achievement/handlers.d.ts +117 -0
- package/dist/tools/achievement/handlers.js +161 -0
- package/dist/tools/achievement/index.d.ts +479 -0
- package/dist/tools/achievement/index.js +74 -0
- package/dist/tools/achievement/schemas.d.ts +433 -0
- package/dist/tools/achievement/schemas.js +142 -0
- package/dist/tools/achievement.d.ts +141 -121
- package/dist/tools/achievement.js +60 -24
- package/dist/tools/admin.d.ts +6 -6
- package/dist/tools/admin.js +12 -12
- package/dist/tools/analytics.d.ts +11 -11
- package/dist/tools/analytics.js +30 -29
- package/dist/tools/apikey.d.ts +3 -3
- package/dist/tools/apikey.js +6 -6
- package/dist/tools/audit.d.ts +2 -2
- package/dist/tools/audit.js +4 -4
- package/dist/tools/badge.d.ts +6 -6
- package/dist/tools/badge.js +23 -18
- package/dist/tools/campaign/handlers.d.ts +42 -0
- package/dist/tools/campaign/handlers.js +223 -0
- package/dist/tools/campaign/index.d.ts +783 -0
- package/dist/tools/campaign/index.js +117 -0
- package/dist/tools/campaign/member-handlers.d.ts +60 -0
- package/dist/tools/campaign/member-handlers.js +159 -0
- package/dist/tools/campaign/schemas.d.ts +704 -0
- package/dist/tools/campaign/schemas.js +259 -0
- package/dist/tools/campaign/types.d.ts +161 -0
- package/dist/tools/campaign/types.js +2 -0
- package/dist/tools/custom-event.d.ts +315 -0
- package/dist/tools/custom-event.js +270 -0
- package/dist/tools/export.d.ts +4 -4
- package/dist/tools/export.js +12 -12
- package/dist/tools/import.d.ts +3 -3
- package/dist/tools/import.js +23 -15
- package/dist/tools/index.js +13 -5
- package/dist/tools/member/handlers.d.ts +111 -0
- package/dist/tools/member/handlers.js +206 -0
- package/dist/tools/member/index.d.ts +169 -0
- package/dist/tools/member/index.js +92 -0
- package/dist/tools/member/schemas.d.ts +89 -0
- package/dist/tools/member/schemas.js +65 -0
- package/dist/tools/points.d.ts +7 -6
- package/dist/tools/points.js +21 -20
- package/dist/tools/referral/handlers.d.ts +47 -0
- package/dist/tools/referral/handlers.js +115 -0
- package/dist/tools/referral/index.d.ts +44 -0
- package/dist/tools/referral/index.js +44 -0
- package/dist/tools/referral/schemas.d.ts +34 -0
- package/dist/tools/referral/schemas.js +52 -0
- package/dist/tools/reward/handlers.d.ts +110 -0
- package/dist/tools/reward/handlers.js +289 -0
- package/dist/tools/reward/index.d.ts +177 -0
- package/dist/tools/reward/index.js +93 -0
- package/dist/tools/reward/schemas.d.ts +116 -0
- package/dist/tools/reward/schemas.js +92 -0
- package/dist/tools/role.d.ts +6 -6
- package/dist/tools/role.js +12 -12
- package/dist/tools/segment/handlers.d.ts +87 -0
- package/dist/tools/segment/handlers.js +174 -0
- package/dist/tools/segment/index.d.ts +395 -0
- package/dist/tools/segment/index.js +88 -0
- package/dist/tools/segment/schemas.d.ts +337 -0
- package/dist/tools/segment/schemas.js +79 -0
- package/dist/tools/segment.d.ts +10 -10
- package/dist/tools/segment.js +55 -31
- package/dist/tools/store.d.ts +4 -4
- package/dist/tools/store.js +8 -8
- package/dist/tools/tierset.d.ts +10 -10
- package/dist/tools/tierset.js +69 -37
- package/dist/tools/transaction.d.ts +4 -4
- package/dist/tools/transaction.js +12 -12
- package/dist/tools/wallet-type.d.ts +221 -16
- package/dist/tools/wallet-type.js +248 -17
- package/dist/tools/webhook.d.ts +6 -6
- package/dist/tools/webhook.js +90 -31
- package/dist/types/schemas/achievement.d.ts +18 -18
- package/dist/types/schemas/campaign.d.ts +64 -184
- package/dist/types/schemas/campaign.js +2 -7
- package/dist/types/schemas/common.d.ts +5 -0
- package/dist/types/schemas/common.js +5 -0
- package/dist/types/schemas/member.d.ts +2 -2
- package/dist/types/schemas/reward.d.ts +94 -18
- package/dist/types/schemas/reward.js +8 -3
- package/dist/types/schemas/wallet-type.d.ts +306 -8
- package/dist/types/schemas/wallet-type.js +82 -1
- package/dist/utils/errors.js +32 -5
- package/dist/workflows/app-login-streak.d.ts +39 -0
- package/dist/workflows/app-login-streak.js +298 -0
- package/dist/workflows/early-arrival.d.ts +33 -0
- package/dist/workflows/early-arrival.js +148 -0
- package/dist/workflows/index.d.ts +101 -0
- package/dist/workflows/index.js +208 -0
- package/dist/workflows/match-attendance.d.ts +45 -0
- package/dist/workflows/match-attendance.js +308 -0
- package/dist/workflows/sportsbar-visit.d.ts +41 -0
- package/dist/workflows/sportsbar-visit.js +284 -0
- package/dist/workflows/vod-watching.d.ts +43 -0
- package/dist/workflows/vod-watching.js +326 -0
- package/package.json +8 -2
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { apiGet } from "../client/http.js";
|
|
3
|
-
import { WalletTypeSchema, WalletTypeListResponseSchema, } from "../types/schemas/wallet-type.js";
|
|
4
|
-
import { formatApiError } from "../utils/errors.js";
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { apiGet, apiPost, apiPut } from "../client/http.js";
|
|
3
|
+
import { WalletTypeSchema, WalletTypeListResponseSchema, WalletTypeCreateResponseSchema, WalletTypeListInputSchema, WalletTypeGetInputSchema, WalletTypeCreateInputSchema, WalletTypeUpdateInputSchema, } from "../types/schemas/wallet-type.js";
|
|
4
|
+
import { formatApiError, OpenLoyaltyError } from "../utils/errors.js";
|
|
5
5
|
import { getStoreCode } from "../config.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
6
|
+
import { omitUndefined } from "../utils/payload.js";
|
|
7
|
+
// Re-export input schemas for tool registration
|
|
8
|
+
export { WalletTypeListInputSchema, WalletTypeGetInputSchema, WalletTypeCreateInputSchema, WalletTypeUpdateInputSchema, };
|
|
9
|
+
// Helper to check if error is an axios error (more reliable than instanceof)
|
|
10
|
+
function isAxiosError(error) {
|
|
11
|
+
return axios.isAxiosError(error);
|
|
12
|
+
}
|
|
13
|
+
// Helper to extract error message from axios or regular errors
|
|
14
|
+
function getErrorMessage(error) {
|
|
15
|
+
if (isAxiosError(error)) {
|
|
16
|
+
const data = error.response?.data;
|
|
17
|
+
return data?.message ? String(data.message) : error.message;
|
|
18
|
+
}
|
|
19
|
+
if (error instanceof Error) {
|
|
20
|
+
return error.message;
|
|
21
|
+
}
|
|
22
|
+
return String(error);
|
|
23
|
+
}
|
|
24
|
+
// Helper to get HTTP status from error
|
|
25
|
+
function getErrorStatus(error) {
|
|
26
|
+
if (isAxiosError(error)) {
|
|
27
|
+
return error.response?.status;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// HANDLER FUNCTIONS
|
|
33
|
+
// =============================================================================
|
|
13
34
|
export async function walletTypeList(input) {
|
|
14
35
|
const storeCode = getStoreCode(input.storeCode);
|
|
15
36
|
try {
|
|
@@ -26,7 +47,7 @@ export async function walletTypeList(input) {
|
|
|
26
47
|
}));
|
|
27
48
|
}
|
|
28
49
|
catch (error) {
|
|
29
|
-
throw formatApiError(error, "
|
|
50
|
+
throw formatApiError(error, "ol_wallet_type_list");
|
|
30
51
|
}
|
|
31
52
|
}
|
|
32
53
|
export async function walletTypeGet(input) {
|
|
@@ -37,24 +58,234 @@ export async function walletTypeGet(input) {
|
|
|
37
58
|
return validated;
|
|
38
59
|
}
|
|
39
60
|
catch (error) {
|
|
40
|
-
|
|
61
|
+
// Check for 404 errors
|
|
62
|
+
if (getErrorStatus(error) === 404) {
|
|
63
|
+
throw new OpenLoyaltyError({
|
|
64
|
+
code: "NOT_FOUND",
|
|
65
|
+
message: `Wallet type with ID '${input.walletTypeId}' not found`,
|
|
66
|
+
hint: `Use ol_wallet_type_list() to see available wallet types. ` +
|
|
67
|
+
`The walletTypeId may be incorrect or the wallet type may have been deleted.`,
|
|
68
|
+
relatedTool: "ol_wallet_type_get",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
throw formatApiError(error, "ol_wallet_type_get");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function walletTypeCreate(input) {
|
|
75
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
76
|
+
// Validate required fields
|
|
77
|
+
if (!input.translations?.en?.name) {
|
|
78
|
+
throw new OpenLoyaltyError({
|
|
79
|
+
code: "VALIDATION_ERROR",
|
|
80
|
+
message: "Missing required field: translations.en.name",
|
|
81
|
+
hint: "translations.en.name is REQUIRED. Example: { translations: { en: { name: 'Bonus Points' } } }",
|
|
82
|
+
relatedTool: "ol_wallet_type_create",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!input.unitSingularName || !input.unitPluralName) {
|
|
86
|
+
throw new OpenLoyaltyError({
|
|
87
|
+
code: "VALIDATION_ERROR",
|
|
88
|
+
message: "Missing required fields: unitSingularName and/or unitPluralName",
|
|
89
|
+
hint: "Both unitSingularName (e.g., 'point') and unitPluralName (e.g., 'points') are REQUIRED.",
|
|
90
|
+
relatedTool: "ol_wallet_type_create",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Validate unitExpiryDate format if provided
|
|
94
|
+
if (input.unitExpiryDate && !/^\d{2}-\d{2}$/.test(input.unitExpiryDate)) {
|
|
95
|
+
throw new OpenLoyaltyError({
|
|
96
|
+
code: "VALIDATION_ERROR",
|
|
97
|
+
message: `Invalid unitExpiryDate format: '${input.unitExpiryDate}'`,
|
|
98
|
+
hint: "unitExpiryDate must be in 'MM-DD' format (e.g., '12-31' for December 31st). " +
|
|
99
|
+
"Do NOT use 'YYYY-MM-DD' format.",
|
|
100
|
+
relatedTool: "ol_wallet_type_create",
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
// Validate unitDaysExpiryAfter is provided (API requires it)
|
|
104
|
+
if (!input.unitDaysExpiryAfter) {
|
|
105
|
+
throw new OpenLoyaltyError({
|
|
106
|
+
code: "VALIDATION_ERROR",
|
|
107
|
+
message: "Missing required field: unitDaysExpiryAfter",
|
|
108
|
+
hint: "unitDaysExpiryAfter is REQUIRED. Use 'all_time_active' for never-expiring points, " +
|
|
109
|
+
"or a number string like '365' for points that expire after N days.",
|
|
110
|
+
relatedTool: "ol_wallet_type_create",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Build payload - API expects { walletType: { ... } }
|
|
114
|
+
// NOTE: 'active' and 'limits' are NOT accepted at creation time - use update after creation
|
|
115
|
+
const walletTypePayload = omitUndefined({
|
|
116
|
+
translations: input.translations,
|
|
117
|
+
unitSingularName: input.unitSingularName,
|
|
118
|
+
unitPluralName: input.unitPluralName,
|
|
119
|
+
code: input.code,
|
|
120
|
+
allowNegativeBalance: input.allowNegativeBalance,
|
|
121
|
+
unitExpiryDate: input.unitExpiryDate,
|
|
122
|
+
unitDaysExpiryAfter: input.unitDaysExpiryAfter,
|
|
123
|
+
unitDaysActiveCount: input.unitDaysActiveCount,
|
|
124
|
+
unitYearsActiveCount: input.unitYearsActiveCount,
|
|
125
|
+
unitDaysLocked: input.unitDaysLocked,
|
|
126
|
+
allTimeNotLocked: input.allTimeNotLocked,
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
const response = await apiPost(`/${storeCode}/walletType`, { walletType: walletTypePayload });
|
|
130
|
+
const validated = WalletTypeCreateResponseSchema.parse(response);
|
|
131
|
+
return {
|
|
132
|
+
walletTypeId: validated.walletTypeId,
|
|
133
|
+
name: input.translations.en.name,
|
|
134
|
+
code: input.code,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
// Check for duplicate code error
|
|
139
|
+
const errorMsg = getErrorMessage(error);
|
|
140
|
+
if (errorMsg.toLowerCase().includes("code") && errorMsg.toLowerCase().includes("already")) {
|
|
141
|
+
throw new OpenLoyaltyError({
|
|
142
|
+
code: "DUPLICATE_CODE",
|
|
143
|
+
message: `Wallet type with code '${input.code}' already exists`,
|
|
144
|
+
hint: `Use a different code value, or use ol_wallet_type_list() to find existing wallet types. ` +
|
|
145
|
+
`Each wallet type must have a unique code.`,
|
|
146
|
+
relatedTool: "ol_wallet_type_create",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
throw formatApiError(error, "ol_wallet_type_create");
|
|
41
150
|
}
|
|
42
151
|
}
|
|
152
|
+
export async function walletTypeUpdate(input) {
|
|
153
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
154
|
+
// Validate unitExpiryDate format if provided
|
|
155
|
+
if (input.unitExpiryDate && !/^\d{2}-\d{2}$/.test(input.unitExpiryDate)) {
|
|
156
|
+
throw new OpenLoyaltyError({
|
|
157
|
+
code: "VALIDATION_ERROR",
|
|
158
|
+
message: `Invalid unitExpiryDate format: '${input.unitExpiryDate}'`,
|
|
159
|
+
hint: "unitExpiryDate must be in 'MM-DD' format (e.g., '12-31' for December 31st). " +
|
|
160
|
+
"Do NOT use 'YYYY-MM-DD' format.",
|
|
161
|
+
relatedTool: "ol_wallet_type_update",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Validate interval types if limits provided
|
|
165
|
+
const validIntervalTypes = ["calendarHours", "calendarDays", "calendarWeeks", "calendarMonths", "calendarYears"];
|
|
166
|
+
if (input.limits?.points?.interval?.type && !validIntervalTypes.includes(input.limits.points.interval.type)) {
|
|
167
|
+
throw new OpenLoyaltyError({
|
|
168
|
+
code: "VALIDATION_ERROR",
|
|
169
|
+
message: `Invalid interval type: '${input.limits.points.interval.type}'`,
|
|
170
|
+
hint: `Valid interval types are: ${validIntervalTypes.join(", ")}. ` +
|
|
171
|
+
`IMPORTANT: Use 'calendarDays' NOT 'days', 'calendarMonths' NOT 'months', etc.`,
|
|
172
|
+
relatedTool: "ol_wallet_type_update",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// API requires full wallet type object - fetch existing then merge
|
|
176
|
+
let existing;
|
|
177
|
+
try {
|
|
178
|
+
existing = await apiGet(`/${storeCode}/walletType/${input.walletTypeId}`);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
// Check for 404 errors
|
|
182
|
+
if (getErrorStatus(error) === 404) {
|
|
183
|
+
throw new OpenLoyaltyError({
|
|
184
|
+
code: "NOT_FOUND",
|
|
185
|
+
message: `Wallet type with ID '${input.walletTypeId}' not found`,
|
|
186
|
+
hint: `Use ol_wallet_type_list() to see available wallet types. ` +
|
|
187
|
+
`The walletTypeId may be incorrect or the wallet type may have been deleted.`,
|
|
188
|
+
relatedTool: "ol_wallet_type_update",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
throw formatApiError(error, "ol_wallet_type_update");
|
|
192
|
+
}
|
|
193
|
+
// Build translations - merge with existing
|
|
194
|
+
const existingTranslations = existing.translations || {};
|
|
195
|
+
const existingEn = existingTranslations.en || {};
|
|
196
|
+
const updatedTranslations = {
|
|
197
|
+
...existingTranslations,
|
|
198
|
+
en: {
|
|
199
|
+
...existingEn,
|
|
200
|
+
...(input.name !== undefined ? { name: input.name } : {}),
|
|
201
|
+
...(input.description !== undefined ? { description: input.description } : {}),
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
// Build payload with existing values as base
|
|
205
|
+
const walletTypePayload = {
|
|
206
|
+
translations: updatedTranslations,
|
|
207
|
+
unitSingularName: input.unitSingularName ?? existing.unitSingularName,
|
|
208
|
+
unitPluralName: input.unitPluralName ?? existing.unitPluralName,
|
|
209
|
+
active: input.active ?? existing.active,
|
|
210
|
+
allowNegativeBalance: input.allowNegativeBalance ?? existing.allowNegativeBalance,
|
|
211
|
+
limits: input.limits ?? existing.limits,
|
|
212
|
+
unitExpiryDate: input.unitExpiryDate ?? existing.unitExpiryDate,
|
|
213
|
+
unitDaysExpiryAfter: input.unitDaysExpiryAfter ?? existing.unitDaysExpiryAfter,
|
|
214
|
+
unitDaysActiveCount: input.unitDaysActiveCount ?? existing.unitDaysActiveCount,
|
|
215
|
+
unitYearsActiveCount: input.unitYearsActiveCount ?? existing.unitYearsActiveCount,
|
|
216
|
+
unitDaysLocked: input.unitDaysLocked ?? existing.unitDaysLocked,
|
|
217
|
+
allTimeNotLocked: input.allTimeNotLocked ?? existing.allTimeNotLocked,
|
|
218
|
+
};
|
|
219
|
+
try {
|
|
220
|
+
await apiPut(`/${storeCode}/walletType/${input.walletTypeId}`, { walletType: walletTypePayload });
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
throw formatApiError(error, "ol_wallet_type_update");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
// =============================================================================
|
|
227
|
+
// TOOL DEFINITIONS
|
|
228
|
+
// =============================================================================
|
|
43
229
|
export const walletTypeToolDefinitions = [
|
|
44
230
|
{
|
|
45
|
-
name: "
|
|
231
|
+
name: "ol_wallet_type_list",
|
|
46
232
|
title: "List Point Currencies",
|
|
47
|
-
description: "List all available wallet types (point currencies).
|
|
233
|
+
description: "List all available wallet types (point currencies). " +
|
|
234
|
+
"Use this to find wallet type codes and IDs for other operations. " +
|
|
235
|
+
"Returns walletTypeId (UUID), code (unique identifier like 'default'), and name for each wallet type. " +
|
|
236
|
+
"💡 TIP: Most stores have a 'default' wallet type for main loyalty points.",
|
|
48
237
|
readOnly: true,
|
|
49
238
|
inputSchema: WalletTypeListInputSchema,
|
|
50
239
|
handler: walletTypeList,
|
|
51
240
|
},
|
|
52
241
|
{
|
|
53
|
-
name: "
|
|
242
|
+
name: "ol_wallet_type_get",
|
|
54
243
|
title: "Get Point Currency Details",
|
|
55
|
-
description: "Get details for a specific wallet type by ID.
|
|
244
|
+
description: "Get full details for a specific wallet type by ID. " +
|
|
245
|
+
"Returns configuration including limits, expiry settings, and translations. " +
|
|
246
|
+
"💡 TIP: Use ol_wallet_type_list() first to find the walletTypeId.",
|
|
56
247
|
readOnly: true,
|
|
57
248
|
inputSchema: WalletTypeGetInputSchema,
|
|
58
249
|
handler: walletTypeGet,
|
|
59
250
|
},
|
|
251
|
+
{
|
|
252
|
+
name: "ol_wallet_type_create",
|
|
253
|
+
title: "Create Point Currency",
|
|
254
|
+
description: "Create a new wallet type (point currency) for the loyalty program. " +
|
|
255
|
+
"⚠️ REQUIRED FIELDS (will fail without these): " +
|
|
256
|
+
"1. translations: { en: { name: 'Currency Name' } } - Name is REQUIRED. " +
|
|
257
|
+
"2. unitSingularName: 'point' (or 'coin', 'star', etc.) - The singular form. " +
|
|
258
|
+
"3. unitPluralName: 'points' (or 'coins', 'stars', etc.) - The plural form. " +
|
|
259
|
+
"4. unitDaysExpiryAfter: 'all_time_active' or number string like '365'. " +
|
|
260
|
+
"📝 OPTIONAL: " +
|
|
261
|
+
"• code: Unique identifier (auto-generated if omitted, cannot change later). " +
|
|
262
|
+
"• allowNegativeBalance: true/false (default: false). " +
|
|
263
|
+
"• unitExpiryDate: Annual expiry in 'MM-DD' format (e.g., '12-31'). " +
|
|
264
|
+
"• unitDaysLocked: Days before points become spendable (0 for immediate). " +
|
|
265
|
+
"⚠️ NOT SUPPORTED AT CREATION: 'active' and 'limits' - use ol_wallet_type_update after creation. " +
|
|
266
|
+
"⏰ NOTE: New wallets are 'blocked' for ~2 minutes after creation - wait before updating. " +
|
|
267
|
+
"💡 EXAMPLE: { translations: { en: { name: 'Bonus Points' } }, unitSingularName: 'point', " +
|
|
268
|
+
"unitPluralName: 'points', unitDaysExpiryAfter: 'all_time_active', code: 'bonus' }",
|
|
269
|
+
readOnly: false,
|
|
270
|
+
inputSchema: WalletTypeCreateInputSchema,
|
|
271
|
+
handler: walletTypeCreate,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "ol_wallet_type_update",
|
|
275
|
+
title: "Update Point Currency",
|
|
276
|
+
description: "Update an existing wallet type's configuration. " +
|
|
277
|
+
"⚠️ IMPORTANT: Only provide fields you want to change - others are preserved. " +
|
|
278
|
+
"⚠️ CANNOT CHANGE: The 'code' field cannot be modified after creation. " +
|
|
279
|
+
"📝 UPDATABLE FIELDS: " +
|
|
280
|
+
"• name/description: Update via the tool parameters (updates translations.en internally). " +
|
|
281
|
+
"• unitSingularName/unitPluralName: Update the display names. " +
|
|
282
|
+
"• active: Enable/disable the wallet type. " +
|
|
283
|
+
"• allowNegativeBalance: Allow/disallow negative balances. " +
|
|
284
|
+
"• limits: Update earning limits (same format as create). " +
|
|
285
|
+
"• Expiry settings: unitExpiryDate, unitDaysExpiryAfter, etc. " +
|
|
286
|
+
"💡 TIP: Use ol_wallet_type_get(walletTypeId) first to see current configuration.",
|
|
287
|
+
readOnly: false,
|
|
288
|
+
inputSchema: WalletTypeUpdateInputSchema,
|
|
289
|
+
handler: walletTypeUpdate,
|
|
290
|
+
},
|
|
60
291
|
];
|
package/dist/tools/webhook.d.ts
CHANGED
|
@@ -104,7 +104,7 @@ export declare function webhookEvents(input: {
|
|
|
104
104
|
storeCode?: string;
|
|
105
105
|
}): Promise<WebhookEventTypesResponse>;
|
|
106
106
|
export declare const webhookToolDefinitions: readonly [{
|
|
107
|
-
readonly name: "
|
|
107
|
+
readonly name: "ol_webhook_list";
|
|
108
108
|
readonly title: "List Webhook Subscriptions";
|
|
109
109
|
readonly description: "List webhook subscriptions with optional filtering. Returns paginated list of subscriptions with webhookSubscriptionId, eventName, url, and createdAt.";
|
|
110
110
|
readonly readOnly: true;
|
|
@@ -117,7 +117,7 @@ export declare const webhookToolDefinitions: readonly [{
|
|
|
117
117
|
};
|
|
118
118
|
readonly handler: typeof webhookList;
|
|
119
119
|
}, {
|
|
120
|
-
readonly name: "
|
|
120
|
+
readonly name: "ol_webhook_create";
|
|
121
121
|
readonly title: "Create Webhook Subscription";
|
|
122
122
|
readonly description: "Create a new webhook subscription to receive event notifications. Returns webhookSubscriptionId on success. Use webhook_events to discover available event types before creating subscriptions.";
|
|
123
123
|
readonly readOnly: false;
|
|
@@ -138,7 +138,7 @@ export declare const webhookToolDefinitions: readonly [{
|
|
|
138
138
|
};
|
|
139
139
|
readonly handler: typeof webhookCreate;
|
|
140
140
|
}, {
|
|
141
|
-
readonly name: "
|
|
141
|
+
readonly name: "ol_webhook_get";
|
|
142
142
|
readonly title: "Get Webhook Subscription Details";
|
|
143
143
|
readonly description: "Get full webhook subscription details including headers configuration.";
|
|
144
144
|
readonly readOnly: true;
|
|
@@ -148,7 +148,7 @@ export declare const webhookToolDefinitions: readonly [{
|
|
|
148
148
|
};
|
|
149
149
|
readonly handler: typeof webhookGet;
|
|
150
150
|
}, {
|
|
151
|
-
readonly name: "
|
|
151
|
+
readonly name: "ol_webhook_update";
|
|
152
152
|
readonly title: "Update Webhook Subscription";
|
|
153
153
|
readonly description: "Update a webhook subscription. Can update eventName, url, and headers. Returns void on success (204 No Content).";
|
|
154
154
|
readonly readOnly: false;
|
|
@@ -170,7 +170,7 @@ export declare const webhookToolDefinitions: readonly [{
|
|
|
170
170
|
};
|
|
171
171
|
readonly handler: typeof webhookUpdate;
|
|
172
172
|
}, {
|
|
173
|
-
readonly name: "
|
|
173
|
+
readonly name: "ol_webhook_delete";
|
|
174
174
|
readonly title: "Delete Webhook Subscription (Permanent)";
|
|
175
175
|
readonly description: "Delete a webhook subscription. Returns void on success (204 No Content). The subscription will stop receiving events immediately.";
|
|
176
176
|
readonly readOnly: false;
|
|
@@ -181,7 +181,7 @@ export declare const webhookToolDefinitions: readonly [{
|
|
|
181
181
|
};
|
|
182
182
|
readonly handler: typeof webhookDelete;
|
|
183
183
|
}, {
|
|
184
|
-
readonly name: "
|
|
184
|
+
readonly name: "ol_webhook_events";
|
|
185
185
|
readonly title: "List Available Webhook Events";
|
|
186
186
|
readonly description: "Get available webhook event types. Returns list of event names that can be used when creating webhook subscriptions. Use this to discover available events before creating subscriptions.";
|
|
187
187
|
readonly readOnly: true;
|
package/dist/tools/webhook.js
CHANGED
|
@@ -1,7 +1,68 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { isIP } from "net";
|
|
2
3
|
import { apiGet, apiPost, apiPut, apiDelete } from "../client/http.js";
|
|
3
4
|
import { formatApiError } from "../utils/errors.js";
|
|
4
5
|
import { getStoreCode } from "../config.js";
|
|
6
|
+
/**
|
|
7
|
+
* Check if an IP address is private, loopback, link-local, or otherwise internal.
|
|
8
|
+
* Blocks IPv4 and IPv6 private ranges to prevent SSRF attacks.
|
|
9
|
+
*/
|
|
10
|
+
function isPrivateIP(ip) {
|
|
11
|
+
const normalizedIP = ip.toLowerCase();
|
|
12
|
+
// IPv4 private/reserved ranges
|
|
13
|
+
if (normalizedIP.startsWith("10.") || // 10.0.0.0/8 private
|
|
14
|
+
normalizedIP.startsWith("192.168.") || // 192.168.0.0/16 private
|
|
15
|
+
normalizedIP.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./) || // 172.16.0.0/12 private
|
|
16
|
+
normalizedIP.startsWith("127.") || // 127.0.0.0/8 loopback
|
|
17
|
+
normalizedIP.startsWith("169.254.") || // 169.254.0.0/16 link-local
|
|
18
|
+
normalizedIP === "0.0.0.0" || // Unspecified
|
|
19
|
+
normalizedIP.startsWith("100.64.") || // 100.64.0.0/10 carrier-grade NAT
|
|
20
|
+
normalizedIP.match(/^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./) // 100.64-127.x.x
|
|
21
|
+
) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
// IPv6 private/reserved ranges
|
|
25
|
+
if (normalizedIP === "::1" || // Loopback
|
|
26
|
+
normalizedIP === "::" || // Unspecified
|
|
27
|
+
normalizedIP.startsWith("fc") || // fc00::/7 unique local (fc00-fdff)
|
|
28
|
+
normalizedIP.startsWith("fd") || // fc00::/7 unique local (fc00-fdff)
|
|
29
|
+
normalizedIP.startsWith("fe80:") || // fe80::/10 link-local
|
|
30
|
+
normalizedIP.startsWith("::ffff:127.") || // IPv4-mapped loopback
|
|
31
|
+
normalizedIP.startsWith("::ffff:10.") || // IPv4-mapped private
|
|
32
|
+
normalizedIP.startsWith("::ffff:192.168.") || // IPv4-mapped private
|
|
33
|
+
normalizedIP.match(/^::ffff:172\.(1[6-9]|2[0-9]|3[0-1])\./) // IPv4-mapped private
|
|
34
|
+
) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if a hostname points to an internal DNS name.
|
|
41
|
+
* Blocks common internal TLDs and patterns.
|
|
42
|
+
*/
|
|
43
|
+
function isInternalHostname(hostname) {
|
|
44
|
+
const lowerHostname = hostname.toLowerCase();
|
|
45
|
+
// Block localhost variants
|
|
46
|
+
if (lowerHostname === "localhost" ||
|
|
47
|
+
lowerHostname.endsWith(".localhost") ||
|
|
48
|
+
lowerHostname.endsWith(".local") ||
|
|
49
|
+
lowerHostname.endsWith(".internal") ||
|
|
50
|
+
lowerHostname.endsWith(".lan") ||
|
|
51
|
+
lowerHostname.endsWith(".home") ||
|
|
52
|
+
lowerHostname.endsWith(".corp") ||
|
|
53
|
+
lowerHostname.endsWith(".intranet")) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Block cloud metadata hostnames
|
|
57
|
+
if (lowerHostname === "metadata.google.internal" ||
|
|
58
|
+
lowerHostname === "metadata" ||
|
|
59
|
+
lowerHostname.endsWith(".metadata") ||
|
|
60
|
+
lowerHostname === "instance-data" ||
|
|
61
|
+
lowerHostname.endsWith(".amazonaws.com") && lowerHostname.includes("metadata")) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
5
66
|
// SSRF protection: validate webhook URLs are external HTTPS endpoints
|
|
6
67
|
function isValidWebhookUrl(url) {
|
|
7
68
|
try {
|
|
@@ -11,24 +72,22 @@ function isValidWebhookUrl(url) {
|
|
|
11
72
|
return false;
|
|
12
73
|
}
|
|
13
74
|
const hostname = parsed.hostname.toLowerCase();
|
|
14
|
-
// Block
|
|
15
|
-
if (hostname
|
|
16
|
-
hostname === "127.0.0.1" ||
|
|
17
|
-
hostname === "0.0.0.0" ||
|
|
18
|
-
hostname === "::1" ||
|
|
19
|
-
hostname.endsWith(".localhost")) {
|
|
75
|
+
// Block internal hostnames
|
|
76
|
+
if (isInternalHostname(hostname)) {
|
|
20
77
|
return false;
|
|
21
78
|
}
|
|
22
|
-
//
|
|
23
|
-
if (hostname
|
|
24
|
-
return
|
|
79
|
+
// If hostname is an IP address, validate it directly
|
|
80
|
+
if (isIP(hostname)) {
|
|
81
|
+
return !isPrivateIP(hostname);
|
|
25
82
|
}
|
|
26
|
-
// Block
|
|
27
|
-
if (hostname
|
|
28
|
-
hostname.startsWith("192.168.") ||
|
|
29
|
-
hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./)) {
|
|
83
|
+
// Block cloud metadata IP
|
|
84
|
+
if (hostname === "169.254.169.254") {
|
|
30
85
|
return false;
|
|
31
86
|
}
|
|
87
|
+
// For DNS hostnames, we can't resolve at validation time (sync function),
|
|
88
|
+
// but we've blocked common internal patterns above.
|
|
89
|
+
// Note: Full DNS rebinding protection would require async DNS resolution,
|
|
90
|
+
// which could be added if this becomes a concern in production.
|
|
32
91
|
return true;
|
|
33
92
|
}
|
|
34
93
|
catch {
|
|
@@ -40,14 +99,14 @@ const webhookUrlSchema = z.string()
|
|
|
40
99
|
.refine(isValidWebhookUrl, "URL must be an external HTTPS endpoint (no localhost, private IPs, or metadata endpoints)");
|
|
41
100
|
// Input Schemas
|
|
42
101
|
export const WebhookListInputSchema = {
|
|
43
|
-
storeCode: z.string().optional().describe("Store code.
|
|
102
|
+
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."),
|
|
44
103
|
page: z.number().optional().describe("Page number (default: 1)."),
|
|
45
104
|
perPage: z.number().optional().describe("Items per page (default: 25)."),
|
|
46
105
|
eventName: z.string().optional().describe("Filter by event name."),
|
|
47
106
|
url: z.string().optional().describe("Filter by URL."),
|
|
48
107
|
};
|
|
49
108
|
export const WebhookCreateInputSchema = {
|
|
50
|
-
storeCode: z.string().optional().describe("Store code.
|
|
109
|
+
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."),
|
|
51
110
|
eventName: z.string().describe("Event name to subscribe to. Use webhook_events to discover available events."),
|
|
52
111
|
url: webhookUrlSchema.describe("HTTPS URL to receive webhook events. Must be an external endpoint (no localhost or private IPs)."),
|
|
53
112
|
headers: z.array(z.object({
|
|
@@ -56,11 +115,11 @@ export const WebhookCreateInputSchema = {
|
|
|
56
115
|
})).optional().describe("Custom headers to include in webhook requests."),
|
|
57
116
|
};
|
|
58
117
|
export const WebhookGetInputSchema = {
|
|
59
|
-
storeCode: z.string().optional().describe("Store code.
|
|
118
|
+
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."),
|
|
60
119
|
webhookSubscriptionId: z.string().describe("The webhook subscription ID (UUID) to retrieve."),
|
|
61
120
|
};
|
|
62
121
|
export const WebhookUpdateInputSchema = {
|
|
63
|
-
storeCode: z.string().optional().describe("Store code.
|
|
122
|
+
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."),
|
|
64
123
|
webhookSubscriptionId: z.string().describe("The webhook subscription ID (UUID) to update."),
|
|
65
124
|
eventName: z.string().optional().describe("Event name to subscribe to."),
|
|
66
125
|
url: webhookUrlSchema.optional().describe("HTTPS URL to receive webhook events. Must be an external endpoint (no localhost or private IPs)."),
|
|
@@ -70,11 +129,11 @@ export const WebhookUpdateInputSchema = {
|
|
|
70
129
|
})).optional().describe("Custom headers to include in webhook requests."),
|
|
71
130
|
};
|
|
72
131
|
export const WebhookDeleteInputSchema = {
|
|
73
|
-
storeCode: z.string().optional().describe("Store code.
|
|
132
|
+
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."),
|
|
74
133
|
webhookSubscriptionId: z.string().describe("The webhook subscription ID (UUID) to delete."),
|
|
75
134
|
};
|
|
76
135
|
export const WebhookEventsInputSchema = {
|
|
77
|
-
storeCode: z.string().optional().describe("Store code.
|
|
136
|
+
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."),
|
|
78
137
|
};
|
|
79
138
|
// Handler functions
|
|
80
139
|
export async function webhookList(input) {
|
|
@@ -95,7 +154,7 @@ export async function webhookList(input) {
|
|
|
95
154
|
return response;
|
|
96
155
|
}
|
|
97
156
|
catch (error) {
|
|
98
|
-
throw formatApiError(error, "
|
|
157
|
+
throw formatApiError(error, "ol_webhook_list");
|
|
99
158
|
}
|
|
100
159
|
}
|
|
101
160
|
export async function webhookCreate(input) {
|
|
@@ -112,7 +171,7 @@ export async function webhookCreate(input) {
|
|
|
112
171
|
return response;
|
|
113
172
|
}
|
|
114
173
|
catch (error) {
|
|
115
|
-
throw formatApiError(error, "
|
|
174
|
+
throw formatApiError(error, "ol_webhook_create");
|
|
116
175
|
}
|
|
117
176
|
}
|
|
118
177
|
export async function webhookGet(input) {
|
|
@@ -122,7 +181,7 @@ export async function webhookGet(input) {
|
|
|
122
181
|
return response;
|
|
123
182
|
}
|
|
124
183
|
catch (error) {
|
|
125
|
-
throw formatApiError(error, "
|
|
184
|
+
throw formatApiError(error, "ol_webhook_get");
|
|
126
185
|
}
|
|
127
186
|
}
|
|
128
187
|
export async function webhookUpdate(input) {
|
|
@@ -138,7 +197,7 @@ export async function webhookUpdate(input) {
|
|
|
138
197
|
await apiPut(`/${storeCode}/webhook/subscription/${input.webhookSubscriptionId}`, { webhookSubscription: payload });
|
|
139
198
|
}
|
|
140
199
|
catch (error) {
|
|
141
|
-
throw formatApiError(error, "
|
|
200
|
+
throw formatApiError(error, "ol_webhook_update");
|
|
142
201
|
}
|
|
143
202
|
}
|
|
144
203
|
export async function webhookDelete(input) {
|
|
@@ -147,7 +206,7 @@ export async function webhookDelete(input) {
|
|
|
147
206
|
await apiDelete(`/${storeCode}/webhook/subscription/${input.webhookSubscriptionId}`);
|
|
148
207
|
}
|
|
149
208
|
catch (error) {
|
|
150
|
-
throw formatApiError(error, "
|
|
209
|
+
throw formatApiError(error, "ol_webhook_delete");
|
|
151
210
|
}
|
|
152
211
|
}
|
|
153
212
|
export async function webhookEvents(input) {
|
|
@@ -157,13 +216,13 @@ export async function webhookEvents(input) {
|
|
|
157
216
|
return response;
|
|
158
217
|
}
|
|
159
218
|
catch (error) {
|
|
160
|
-
throw formatApiError(error, "
|
|
219
|
+
throw formatApiError(error, "ol_webhook_events");
|
|
161
220
|
}
|
|
162
221
|
}
|
|
163
222
|
// Tool definitions
|
|
164
223
|
export const webhookToolDefinitions = [
|
|
165
224
|
{
|
|
166
|
-
name: "
|
|
225
|
+
name: "ol_webhook_list",
|
|
167
226
|
title: "List Webhook Subscriptions",
|
|
168
227
|
description: "List webhook subscriptions with optional filtering. Returns paginated list of subscriptions with webhookSubscriptionId, eventName, url, and createdAt.",
|
|
169
228
|
readOnly: true,
|
|
@@ -171,7 +230,7 @@ export const webhookToolDefinitions = [
|
|
|
171
230
|
handler: webhookList,
|
|
172
231
|
},
|
|
173
232
|
{
|
|
174
|
-
name: "
|
|
233
|
+
name: "ol_webhook_create",
|
|
175
234
|
title: "Create Webhook Subscription",
|
|
176
235
|
description: "Create a new webhook subscription to receive event notifications. Returns webhookSubscriptionId on success. Use webhook_events to discover available event types before creating subscriptions.",
|
|
177
236
|
readOnly: false,
|
|
@@ -179,7 +238,7 @@ export const webhookToolDefinitions = [
|
|
|
179
238
|
handler: webhookCreate,
|
|
180
239
|
},
|
|
181
240
|
{
|
|
182
|
-
name: "
|
|
241
|
+
name: "ol_webhook_get",
|
|
183
242
|
title: "Get Webhook Subscription Details",
|
|
184
243
|
description: "Get full webhook subscription details including headers configuration.",
|
|
185
244
|
readOnly: true,
|
|
@@ -187,7 +246,7 @@ export const webhookToolDefinitions = [
|
|
|
187
246
|
handler: webhookGet,
|
|
188
247
|
},
|
|
189
248
|
{
|
|
190
|
-
name: "
|
|
249
|
+
name: "ol_webhook_update",
|
|
191
250
|
title: "Update Webhook Subscription",
|
|
192
251
|
description: "Update a webhook subscription. Can update eventName, url, and headers. Returns void on success (204 No Content).",
|
|
193
252
|
readOnly: false,
|
|
@@ -195,7 +254,7 @@ export const webhookToolDefinitions = [
|
|
|
195
254
|
handler: webhookUpdate,
|
|
196
255
|
},
|
|
197
256
|
{
|
|
198
|
-
name: "
|
|
257
|
+
name: "ol_webhook_delete",
|
|
199
258
|
title: "Delete Webhook Subscription (Permanent)",
|
|
200
259
|
description: "Delete a webhook subscription. Returns void on success (204 No Content). The subscription will stop receiving events immediately.",
|
|
201
260
|
readOnly: false,
|
|
@@ -204,7 +263,7 @@ export const webhookToolDefinitions = [
|
|
|
204
263
|
handler: webhookDelete,
|
|
205
264
|
},
|
|
206
265
|
{
|
|
207
|
-
name: "
|
|
266
|
+
name: "ol_webhook_events",
|
|
208
267
|
title: "List Available Webhook Events",
|
|
209
268
|
description: "Get available webhook event types. Returns list of event names that can be used when creating webhook subscriptions. Use this to discover available events before creating subscriptions.",
|
|
210
269
|
readOnly: true,
|