@open-loyalty/mcp-server 1.8.0 → 1.13.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.
- package/dist/config.d.ts +0 -9
- package/dist/config.js +0 -23
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +58 -22
- package/dist/tools/apps/rewards-catalog/handlers.js +2 -1
- package/dist/tools/campaign/index.d.ts +16 -3
- package/dist/tools/campaign/index.js +15 -4
- package/dist/tools/campaign/member-handlers.d.ts +12 -0
- package/dist/tools/campaign/member-handlers.js +33 -0
- package/dist/tools/campaign/schemas.d.ts +6 -0
- package/dist/tools/campaign/schemas.js +6 -0
- package/dist/tools/channel/handlers.d.ts +32 -0
- package/dist/tools/channel/handlers.js +130 -0
- package/dist/tools/channel/index.d.ts +68 -0
- package/dist/tools/channel/index.js +59 -0
- package/dist/tools/channel/schemas.d.ts +29 -0
- package/dist/tools/channel/schemas.js +30 -0
- package/dist/tools/context/handlers.d.ts +49 -0
- package/dist/tools/context/handlers.js +131 -0
- package/dist/tools/context/index.d.ts +15 -0
- package/dist/tools/context/index.js +20 -0
- package/dist/tools/context/schemas.d.ts +7 -0
- package/dist/tools/context/schemas.js +4 -0
- package/dist/tools/group-of-values/handlers.d.ts +39 -0
- package/dist/tools/group-of-values/handlers.js +133 -0
- package/dist/tools/group-of-values/index.d.ts +82 -0
- package/dist/tools/group-of-values/index.js +72 -0
- package/dist/tools/group-of-values/schemas.d.ts +36 -0
- package/dist/tools/group-of-values/schemas.js +39 -0
- package/dist/tools/index.js +12 -0
- package/dist/tools/language/handlers.d.ts +24 -0
- package/dist/tools/language/handlers.js +127 -0
- package/dist/tools/language/index.d.ts +64 -0
- package/dist/tools/language/index.js +60 -0
- package/dist/tools/language/schemas.d.ts +25 -0
- package/dist/tools/language/schemas.js +25 -0
- package/dist/tools/member/handlers.d.ts +4 -0
- package/dist/tools/member/handlers.js +27 -0
- package/dist/tools/member/index.d.ts +14 -2
- package/dist/tools/member/index.js +15 -2
- package/dist/tools/points/fraud-handlers.d.ts +21 -0
- package/dist/tools/points/fraud-handlers.js +96 -0
- package/dist/tools/points/index.d.ts +50 -1
- package/dist/tools/points/index.js +45 -2
- package/dist/tools/points/schemas.d.ts +11 -0
- package/dist/tools/points/schemas.js +11 -0
- package/dist/tools/reward/category-handlers.d.ts +27 -0
- package/dist/tools/reward/category-handlers.js +70 -0
- package/dist/tools/reward/handlers.d.ts +0 -12
- package/dist/tools/reward/handlers.js +0 -28
- package/dist/tools/reward/index.d.ts +76 -3
- package/dist/tools/reward/index.js +63 -4
- package/dist/tools/reward/photo-handlers.d.ts +10 -0
- package/dist/tools/reward/photo-handlers.js +97 -0
- package/dist/tools/reward/redemption-handlers.d.ts +23 -0
- package/dist/tools/reward/redemption-handlers.js +50 -0
- package/dist/tools/reward/schemas.d.ts +31 -0
- package/dist/tools/reward/schemas.js +33 -0
- package/dist/tools/segment/handlers.js +14 -10
- package/dist/tools/segment/index.js +1 -1
- package/dist/tools/segment/schemas.js +3 -3
- package/dist/tools/store/handlers.d.ts +24 -0
- package/dist/tools/store/handlers.js +29 -1
- package/dist/tools/store/index.d.ts +41 -3
- package/dist/tools/store/index.js +27 -4
- package/dist/tools/store/schemas.d.ts +24 -0
- package/dist/tools/store/schemas.js +24 -0
- package/package.json +2 -12
- package/dist/auth/provider.d.ts +0 -33
- package/dist/auth/provider.js +0 -383
- package/dist/auth/storage.d.ts +0 -16
- package/dist/auth/storage.js +0 -120
- package/dist/http.d.ts +0 -2
- package/dist/http.js +0 -319
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { apiDelete } from "../../client/http.js";
|
|
2
|
+
import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
|
|
3
|
+
import { getConfig, getStoreCode } from "../../config.js";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
import FormDataNode from "form-data";
|
|
6
|
+
export async function rewardPhotoUpload(input) {
|
|
7
|
+
const config = getConfig();
|
|
8
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
9
|
+
// Fetch the image from the provided URL
|
|
10
|
+
let imageBuffer;
|
|
11
|
+
let contentType;
|
|
12
|
+
let fileName;
|
|
13
|
+
try {
|
|
14
|
+
const imageResponse = await axios.get(input.photoUrl, {
|
|
15
|
+
responseType: "arraybuffer",
|
|
16
|
+
timeout: 30000,
|
|
17
|
+
});
|
|
18
|
+
imageBuffer = Buffer.from(imageResponse.data);
|
|
19
|
+
contentType = imageResponse.headers["content-type"] || "image/jpeg";
|
|
20
|
+
// Extract file name from URL or use default
|
|
21
|
+
const urlPath = new URL(input.photoUrl).pathname;
|
|
22
|
+
fileName = urlPath.split("/").pop() || "photo.jpg";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new OpenLoyaltyError({
|
|
26
|
+
code: "PHOTO_FETCH_FAILED",
|
|
27
|
+
message: `Failed to fetch image from URL: ${input.photoUrl}`,
|
|
28
|
+
hint: "Ensure the URL is publicly accessible and points to a valid image (PNG, JPEG, or GIF). Max size: 2MB.",
|
|
29
|
+
relatedTool: "ol_reward_photo_upload",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Validate content type
|
|
33
|
+
const allowed = ["image/png", "image/gif", "image/jpeg", "image/jpg"];
|
|
34
|
+
if (!allowed.some(t => contentType.startsWith(t.split("/")[0] + "/" + t.split("/")[1]))) {
|
|
35
|
+
const normalizedType = contentType.toLowerCase();
|
|
36
|
+
if (!allowed.includes(normalizedType)) {
|
|
37
|
+
throw new OpenLoyaltyError({
|
|
38
|
+
code: "INVALID_PHOTO_TYPE",
|
|
39
|
+
message: `Image type '${contentType}' is not allowed`,
|
|
40
|
+
hint: "Only PNG, JPEG, and GIF images are accepted. Ensure the URL points to a valid image file.",
|
|
41
|
+
relatedTool: "ol_reward_photo_upload",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Upload as multipart form data — field name: photo[file]
|
|
46
|
+
const formData = new FormDataNode();
|
|
47
|
+
formData.append("photo[file]", imageBuffer, {
|
|
48
|
+
filename: fileName,
|
|
49
|
+
contentType: contentType,
|
|
50
|
+
});
|
|
51
|
+
try {
|
|
52
|
+
await axios.post(`${config.apiUrl}/${storeCode}/reward/${input.rewardId}/photo`, formData, {
|
|
53
|
+
headers: {
|
|
54
|
+
"X-AUTH-TOKEN": config.apiToken,
|
|
55
|
+
...formData.getHeaders(),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (axios.isAxiosError(error)) {
|
|
61
|
+
if (error.response?.status === 404) {
|
|
62
|
+
throw new OpenLoyaltyError({
|
|
63
|
+
code: "NOT_FOUND",
|
|
64
|
+
message: `Reward '${input.rewardId}' not found`,
|
|
65
|
+
hint: "Use ol_reward_list() to find existing rewards and their IDs.",
|
|
66
|
+
relatedTool: "ol_reward_photo_upload",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (error.response?.status === 413) {
|
|
70
|
+
throw new OpenLoyaltyError({
|
|
71
|
+
code: "PHOTO_TOO_LARGE",
|
|
72
|
+
message: "The photo exceeds the 2MB size limit",
|
|
73
|
+
hint: "Compress or resize the image before uploading. Maximum allowed size is 2MB.",
|
|
74
|
+
relatedTool: "ol_reward_photo_upload",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
throw formatApiError(error, "ol_reward_photo_upload");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export async function rewardPhotoDelete(input) {
|
|
82
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
83
|
+
try {
|
|
84
|
+
await apiDelete(`/${storeCode}/reward/${input.rewardId}/photo/${input.photoId}`);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
|
88
|
+
throw new OpenLoyaltyError({
|
|
89
|
+
code: "NOT_FOUND",
|
|
90
|
+
message: `Reward photo not found (rewardId: ${input.rewardId}, photoId: ${input.photoId})`,
|
|
91
|
+
hint: "Use ol_reward_get() to see the reward's current photos and their photoIds.",
|
|
92
|
+
relatedTool: "ol_reward_photo_delete",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
throw formatApiError(error, "ol_reward_photo_delete");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare function rewardGetIssuedRewards(input: {
|
|
2
|
+
storeCode?: string;
|
|
3
|
+
memberId: string;
|
|
4
|
+
page?: number;
|
|
5
|
+
perPage?: number;
|
|
6
|
+
status?: string;
|
|
7
|
+
rewardType?: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
issuedRewards: Array<{
|
|
10
|
+
issuedRewardId: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
costInPoints?: number;
|
|
14
|
+
rewardType?: string;
|
|
15
|
+
token?: string;
|
|
16
|
+
redemptionDate?: string;
|
|
17
|
+
cancelledAt?: string;
|
|
18
|
+
}>;
|
|
19
|
+
total: {
|
|
20
|
+
all?: number;
|
|
21
|
+
filtered?: number;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { apiGet } from "../../client/http.js";
|
|
2
|
+
import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
|
|
3
|
+
import { getStoreCode } from "../../config.js";
|
|
4
|
+
export async function rewardGetIssuedRewards(input) {
|
|
5
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
6
|
+
const params = new URLSearchParams();
|
|
7
|
+
params.append("customerId", input.memberId);
|
|
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.status)
|
|
13
|
+
params.append("status", input.status);
|
|
14
|
+
if (input.rewardType)
|
|
15
|
+
params.append("rewardType", input.rewardType);
|
|
16
|
+
const url = `/${storeCode}/redemption?${params.toString()}`;
|
|
17
|
+
try {
|
|
18
|
+
const response = await apiGet(url);
|
|
19
|
+
const issuedRewards = (response.items || []).map((item) => ({
|
|
20
|
+
issuedRewardId: item.issuedRewardId,
|
|
21
|
+
name: item.name,
|
|
22
|
+
status: item.status,
|
|
23
|
+
costInPoints: item.costInPoints,
|
|
24
|
+
rewardType: item.rewardType,
|
|
25
|
+
token: item.token,
|
|
26
|
+
redemptionDate: item.redemptionDate,
|
|
27
|
+
cancelledAt: item.cancelledAt,
|
|
28
|
+
}));
|
|
29
|
+
const total = response.total || {};
|
|
30
|
+
return {
|
|
31
|
+
issuedRewards,
|
|
32
|
+
total: {
|
|
33
|
+
all: typeof total.all === "number" ? total.all : undefined,
|
|
34
|
+
filtered: typeof total.filtered === "number" ? total.filtered : undefined,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const axiosError = error;
|
|
40
|
+
if (axiosError.response?.status === 404) {
|
|
41
|
+
throw new OpenLoyaltyError({
|
|
42
|
+
code: "NOT_FOUND",
|
|
43
|
+
message: `Member '${input.memberId}' not found`,
|
|
44
|
+
hint: "Use ol_member_list() to find the member by email, name, or phone.",
|
|
45
|
+
relatedTool: "ol_member_get_issued_rewards",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
throw formatApiError(error, "ol_member_get_issued_rewards");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -107,3 +107,34 @@ export declare const RewardCategoryListInputSchema: {
|
|
|
107
107
|
perPage: z.ZodOptional<z.ZodNumber>;
|
|
108
108
|
active: z.ZodOptional<z.ZodBoolean>;
|
|
109
109
|
};
|
|
110
|
+
export declare const RewardCategoryCreateInputSchema: {
|
|
111
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
112
|
+
name: z.ZodString;
|
|
113
|
+
active: z.ZodOptional<z.ZodBoolean>;
|
|
114
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
115
|
+
};
|
|
116
|
+
export declare const RewardCategoryUpdateInputSchema: {
|
|
117
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
118
|
+
categoryId: z.ZodString;
|
|
119
|
+
name: z.ZodString;
|
|
120
|
+
active: z.ZodOptional<z.ZodBoolean>;
|
|
121
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
122
|
+
};
|
|
123
|
+
export declare const RewardGetIssuedRewardsInputSchema: {
|
|
124
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
125
|
+
memberId: z.ZodString;
|
|
126
|
+
page: z.ZodOptional<z.ZodNumber>;
|
|
127
|
+
perPage: z.ZodOptional<z.ZodNumber>;
|
|
128
|
+
status: z.ZodOptional<z.ZodString>;
|
|
129
|
+
rewardType: z.ZodOptional<z.ZodString>;
|
|
130
|
+
};
|
|
131
|
+
export declare const RewardPhotoUploadInputSchema: {
|
|
132
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
133
|
+
rewardId: z.ZodString;
|
|
134
|
+
photoUrl: z.ZodString;
|
|
135
|
+
};
|
|
136
|
+
export declare const RewardPhotoDeleteInputSchema: {
|
|
137
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
138
|
+
rewardId: z.ZodString;
|
|
139
|
+
photoId: z.ZodString;
|
|
140
|
+
};
|
|
@@ -105,3 +105,36 @@ export const RewardCategoryListInputSchema = {
|
|
|
105
105
|
perPage: z.number().optional().describe("Items per page (default: 10)."),
|
|
106
106
|
active: z.boolean().optional().describe("Filter by active status."),
|
|
107
107
|
};
|
|
108
|
+
export const RewardCategoryCreateInputSchema = {
|
|
109
|
+
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."),
|
|
110
|
+
name: z.string().describe("Category name in English (required)."),
|
|
111
|
+
active: z.boolean().optional().describe("Whether category is active (default: true)."),
|
|
112
|
+
sortOrder: z.number().optional().describe("Display sort order (0 = first, higher = later). Default: 0."),
|
|
113
|
+
};
|
|
114
|
+
export const RewardCategoryUpdateInputSchema = {
|
|
115
|
+
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."),
|
|
116
|
+
categoryId: z.string().describe("The reward category ID (UUID) to update."),
|
|
117
|
+
name: z.string().describe("Category name in English (required)."),
|
|
118
|
+
active: z.boolean().optional().describe("Whether category is active."),
|
|
119
|
+
sortOrder: z.number().optional().describe("Display sort order (0 = first)."),
|
|
120
|
+
};
|
|
121
|
+
export const RewardGetIssuedRewardsInputSchema = {
|
|
122
|
+
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."),
|
|
123
|
+
memberId: z.string().describe("Member ID (UUID) whose issued/redeemed rewards to list."),
|
|
124
|
+
page: z.number().optional().describe("Page number (default: 1)."),
|
|
125
|
+
perPage: z.number().optional().describe("Items per page (default: 10)."),
|
|
126
|
+
status: z.string().optional().describe("Filter by status (e.g., 'pending', 'fulfilled', 'cancelled')."),
|
|
127
|
+
rewardType: z.string().optional().describe("Filter by reward type (e.g., 'material', 'static_coupon')."),
|
|
128
|
+
};
|
|
129
|
+
export const RewardPhotoUploadInputSchema = {
|
|
130
|
+
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."),
|
|
131
|
+
rewardId: z.string().describe("Reward ID (UUID) to attach the photo to."),
|
|
132
|
+
photoUrl: z.string().describe("Publicly accessible URL of the image to upload. " +
|
|
133
|
+
"Allowed types: PNG, JPEG, GIF. Max size: 2MB. " +
|
|
134
|
+
"The handler will fetch and upload the image automatically."),
|
|
135
|
+
};
|
|
136
|
+
export const RewardPhotoDeleteInputSchema = {
|
|
137
|
+
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."),
|
|
138
|
+
rewardId: z.string().describe("Reward ID (UUID) the photo belongs to."),
|
|
139
|
+
photoId: z.string().describe("Photo ID (UUID) to delete. Use ol_reward_get to find existing photoIds."),
|
|
140
|
+
};
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { apiGet, apiPost, apiPut, apiDelete } from "../../client/http.js";
|
|
2
2
|
import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
|
|
3
3
|
import { getStoreCode } from "../../config.js";
|
|
4
|
-
|
|
5
|
-
function normalizeSegmentParts(parts) {
|
|
4
|
+
function validateSegmentParts(parts, toolName) {
|
|
6
5
|
return parts.map((part) => ({
|
|
7
6
|
...part,
|
|
8
7
|
criteria: part.criteria.map((criterion) => {
|
|
9
8
|
if (!criterion || typeof criterion !== "object") {
|
|
10
9
|
return criterion;
|
|
11
10
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
if (criterion.type === "transaction_count" &&
|
|
12
|
+
criterion.min !== undefined &&
|
|
13
|
+
criterion.max === undefined) {
|
|
14
|
+
throw new OpenLoyaltyError({
|
|
15
|
+
code: "MISSING_MAX_VALUE",
|
|
16
|
+
message: "transaction_count criterion requires both min and max values",
|
|
17
|
+
hint: "Provide an explicit max to define the upper bound. Use max: 999999 for 'min or more' behavior. " +
|
|
18
|
+
"Example: { type: 'transaction_count', min: 5, max: 999999 } for '5 or more purchases'.",
|
|
19
|
+
relatedTool: toolName,
|
|
20
|
+
});
|
|
17
21
|
}
|
|
18
|
-
return
|
|
22
|
+
return criterion;
|
|
19
23
|
}),
|
|
20
24
|
}));
|
|
21
25
|
}
|
|
@@ -59,7 +63,7 @@ export async function segmentCreate(input) {
|
|
|
59
63
|
const storeCode = getStoreCode(input.storeCode);
|
|
60
64
|
const segmentPayload = {
|
|
61
65
|
name: input.name,
|
|
62
|
-
parts:
|
|
66
|
+
parts: validateSegmentParts(input.parts, "ol_segment_create"),
|
|
63
67
|
};
|
|
64
68
|
if (input.description)
|
|
65
69
|
segmentPayload.description = input.description;
|
|
@@ -116,7 +120,7 @@ export async function segmentUpdate(input) {
|
|
|
116
120
|
const storeCode = getStoreCode(input.storeCode);
|
|
117
121
|
const segmentPayload = {
|
|
118
122
|
name: input.name,
|
|
119
|
-
parts:
|
|
123
|
+
parts: validateSegmentParts(input.parts, "ol_segment_update"),
|
|
120
124
|
};
|
|
121
125
|
if (input.description !== undefined)
|
|
122
126
|
segmentPayload.description = input.description;
|
|
@@ -26,7 +26,7 @@ export const segmentToolDefinitions = [
|
|
|
26
26
|
"3. Intended use -- campaign targeting (activate immediately) or analytics only (can stay inactive)? " +
|
|
27
27
|
"REQUIRED: name, parts array with criteria. Parts use OR logic, criteria within parts use AND logic. " +
|
|
28
28
|
"CRITERION FIELDS BY TYPE: " +
|
|
29
|
-
"transaction_count: { type: 'transaction_count', min: N, max: N } (
|
|
29
|
+
"transaction_count: { type: 'transaction_count', min: N, max: N } (BOTH required; use max: 999999 for 'min or more' behavior). " +
|
|
30
30
|
"purchase_in_period: { type: 'purchase_in_period', days: N, min: N, max: N }. " +
|
|
31
31
|
"average_transaction_value: { type: 'average_transaction_value', min: N, max: N }. " +
|
|
32
32
|
"NOTE: 'tier' and 'points_balance' types are REJECTED by the API despite being documented.",
|
|
@@ -9,17 +9,17 @@ export const SegmentListInputSchema = {
|
|
|
9
9
|
};
|
|
10
10
|
// Criterion input schema - flexible to support all criterion types
|
|
11
11
|
// NOTE: Only certain criterion types are supported by the API.
|
|
12
|
-
// WORKING: transaction_count (requires
|
|
12
|
+
// WORKING: transaction_count (requires BOTH min AND max — omitting max throws MISSING_MAX_VALUE error)
|
|
13
13
|
// NOT WORKING: tier, points_balance (rejected by API despite being documented)
|
|
14
14
|
const SegmentCriterionInputSchema = z.object({
|
|
15
|
-
type: z.string().describe("Criterion type.
|
|
15
|
+
type: z.string().describe("Criterion type. 'transaction_count' requires BOTH min AND max (omitting max is an error). NOT WORKING: 'tier', 'points_balance' are rejected by the API."),
|
|
16
16
|
criterionId: z.string().optional().describe("Criterion ID (optional, generated if not provided)."),
|
|
17
17
|
// Common criterion data fields
|
|
18
18
|
days: z.number().optional().describe("Days for time-based criteria (e.g., 'in the last N days'). Used with transaction_count, purchase_period criteria."),
|
|
19
19
|
fromDate: z.string().optional().describe("Start date (ISO format)."),
|
|
20
20
|
toDate: z.string().optional().describe("End date (ISO format)."),
|
|
21
21
|
min: z.number().optional().describe("Minimum value (e.g., min=5 for 'at least 5 transactions'). Required for transaction_count type. BUSINESS IMPACT: Too low a threshold (e.g., min=2 transactions) captures most members -- segment becomes too broad, wasting campaign budget."),
|
|
22
|
-
max: z.number().optional().describe("Maximum value.
|
|
22
|
+
max: z.number().optional().describe("Maximum value. REQUIRED for transaction_count type (omitting max throws an error). Use max: 999999 for 'min or more' behavior. BUSINESS IMPACT: Too narrow a range (e.g., max=3 with min=3) captures very few members -- limited campaign impact."),
|
|
23
23
|
posIds: z.array(z.string()).optional().describe("POS IDs."),
|
|
24
24
|
skus: z.array(z.string()).optional().describe("SKU codes."),
|
|
25
25
|
makers: z.array(z.string()).optional().describe("Maker/brand names."),
|
|
@@ -23,3 +23,27 @@ export declare function storeUpdate(input: {
|
|
|
23
23
|
active?: boolean;
|
|
24
24
|
currency?: string;
|
|
25
25
|
}): Promise<void>;
|
|
26
|
+
export declare function storeGetSettings(input: {
|
|
27
|
+
storeCode?: string;
|
|
28
|
+
}): Promise<Record<string, unknown>>;
|
|
29
|
+
export declare function storeUpdateSettings(input: {
|
|
30
|
+
storeCode?: string;
|
|
31
|
+
programName?: string;
|
|
32
|
+
allowCustomersProfileEdits?: boolean;
|
|
33
|
+
tierAssignType?: "points" | "transactions";
|
|
34
|
+
tierWalletCode?: string;
|
|
35
|
+
excludeDeliveryCostsFromTierAssignment?: boolean;
|
|
36
|
+
excludedLevelCategories?: string[];
|
|
37
|
+
levelDowngradeMode?: "none" | "automatic" | "after_x_days";
|
|
38
|
+
levelDowngradeDays?: number;
|
|
39
|
+
levelDowngradeBase?: "none" | "active_points" | "earned_points" | "earned_points_since_last_level_change";
|
|
40
|
+
levelResetPointsOnDowngrade?: boolean;
|
|
41
|
+
accountActivationRequired?: boolean;
|
|
42
|
+
identificationMethod?: "email" | "phone" | "loyaltyCardNumber";
|
|
43
|
+
timezone?: string;
|
|
44
|
+
timezoneStrategy?: "none" | "default" | "localtime";
|
|
45
|
+
rewardWalletCode?: string;
|
|
46
|
+
expirePointsNotificationDays?: number;
|
|
47
|
+
expireCouponsNotificationDays?: number;
|
|
48
|
+
expireLevelsNotificationDays?: number;
|
|
49
|
+
}): Promise<Record<string, unknown>>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { apiGet, apiPost, apiPut } from "../../client/http.js";
|
|
1
|
+
import { apiGet, apiPost, apiPut, apiPatch } from "../../client/http.js";
|
|
2
2
|
import { formatApiError, OpenLoyaltyError } from "../../utils/errors.js";
|
|
3
3
|
import axios from "axios";
|
|
4
|
+
import { getStoreCode } from "../../config.js";
|
|
4
5
|
export async function storeList(input) {
|
|
5
6
|
const params = new URLSearchParams();
|
|
6
7
|
if (input.page)
|
|
@@ -87,3 +88,30 @@ export async function storeUpdate(input) {
|
|
|
87
88
|
throw formatApiError(error, "ol_store_update");
|
|
88
89
|
}
|
|
89
90
|
}
|
|
91
|
+
export async function storeGetSettings(input) {
|
|
92
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
93
|
+
try {
|
|
94
|
+
const response = await apiGet(`/${storeCode}/settings`);
|
|
95
|
+
return response.settings ?? response;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw formatApiError(error, "ol_store_get_settings");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function storeUpdateSettings(input) {
|
|
102
|
+
const storeCode = getStoreCode(input.storeCode);
|
|
103
|
+
const { storeCode: _sc, ...fields } = input;
|
|
104
|
+
const settings = {};
|
|
105
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
106
|
+
if (value !== undefined) {
|
|
107
|
+
settings[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
const response = await apiPatch(`/${storeCode}/settings`, { settings });
|
|
112
|
+
return response?.settings ?? response;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw formatApiError(error, "ol_store_update_settings");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, } from "./schemas.js";
|
|
1
|
+
export { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, StoreGetSettingsInputSchema, StoreUpdateSettingsInputSchema, } from "./schemas.js";
|
|
2
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";
|
|
3
|
+
export { storeList, storeCreate, storeGet, storeUpdate, storeGetSettings, storeUpdateSettings, } from "./handlers.js";
|
|
4
|
+
import { storeList, storeCreate, storeGet, storeUpdate, storeGetSettings, storeUpdateSettings } from "./handlers.js";
|
|
5
5
|
export declare const storeToolDefinitions: readonly [{
|
|
6
6
|
readonly name: "ol_store_list";
|
|
7
7
|
readonly title: "List Stores";
|
|
@@ -52,4 +52,42 @@ export declare const storeToolDefinitions: readonly [{
|
|
|
52
52
|
currency: import("zod").ZodOptional<import("zod").ZodString>;
|
|
53
53
|
};
|
|
54
54
|
readonly handler: typeof storeUpdate;
|
|
55
|
+
}, {
|
|
56
|
+
readonly name: "ol_store_get_settings";
|
|
57
|
+
readonly title: "Get Store Settings";
|
|
58
|
+
readonly description: string;
|
|
59
|
+
readonly readOnly: true;
|
|
60
|
+
readonly idempotent: true;
|
|
61
|
+
readonly inputSchema: {
|
|
62
|
+
storeCode: import("zod").ZodOptional<import("zod").ZodString>;
|
|
63
|
+
};
|
|
64
|
+
readonly handler: typeof storeGetSettings;
|
|
65
|
+
}, {
|
|
66
|
+
readonly name: "ol_store_update_settings";
|
|
67
|
+
readonly title: "Update Store Settings";
|
|
68
|
+
readonly description: string;
|
|
69
|
+
readonly readOnly: false;
|
|
70
|
+
readonly idempotent: true;
|
|
71
|
+
readonly inputSchema: {
|
|
72
|
+
storeCode: import("zod").ZodOptional<import("zod").ZodString>;
|
|
73
|
+
programName: import("zod").ZodOptional<import("zod").ZodString>;
|
|
74
|
+
allowCustomersProfileEdits: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
75
|
+
tierAssignType: import("zod").ZodOptional<import("zod").ZodEnum<["points", "transactions"]>>;
|
|
76
|
+
tierWalletCode: import("zod").ZodOptional<import("zod").ZodString>;
|
|
77
|
+
excludeDeliveryCostsFromTierAssignment: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
78
|
+
excludedLevelCategories: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString, "many">>;
|
|
79
|
+
levelDowngradeMode: import("zod").ZodOptional<import("zod").ZodEnum<["none", "automatic", "after_x_days"]>>;
|
|
80
|
+
levelDowngradeDays: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
81
|
+
levelDowngradeBase: import("zod").ZodOptional<import("zod").ZodEnum<["none", "active_points", "earned_points", "earned_points_since_last_level_change"]>>;
|
|
82
|
+
levelResetPointsOnDowngrade: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
83
|
+
accountActivationRequired: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
84
|
+
identificationMethod: import("zod").ZodOptional<import("zod").ZodEnum<["email", "phone", "loyaltyCardNumber"]>>;
|
|
85
|
+
timezone: import("zod").ZodOptional<import("zod").ZodString>;
|
|
86
|
+
timezoneStrategy: import("zod").ZodOptional<import("zod").ZodEnum<["none", "default", "localtime"]>>;
|
|
87
|
+
rewardWalletCode: import("zod").ZodOptional<import("zod").ZodString>;
|
|
88
|
+
expirePointsNotificationDays: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
89
|
+
expireCouponsNotificationDays: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
90
|
+
expireLevelsNotificationDays: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
91
|
+
};
|
|
92
|
+
readonly handler: typeof storeUpdateSettings;
|
|
55
93
|
}];
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// Re-export schemas and types
|
|
2
|
-
export { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, } from "./schemas.js";
|
|
2
|
+
export { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, StoreGetSettingsInputSchema, StoreUpdateSettingsInputSchema, } from "./schemas.js";
|
|
3
3
|
// Re-export handlers
|
|
4
|
-
export { storeList, storeCreate, storeGet, storeUpdate, } from "./handlers.js";
|
|
4
|
+
export { storeList, storeCreate, storeGet, storeUpdate, storeGetSettings, storeUpdateSettings, } from "./handlers.js";
|
|
5
5
|
// Imports for tool definitions
|
|
6
|
-
import { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, } from "./schemas.js";
|
|
7
|
-
import { storeList, storeCreate, storeGet, storeUpdate, } from "./handlers.js";
|
|
6
|
+
import { StoreListInputSchema, StoreCreateInputSchema, StoreGetInputSchema, StoreUpdateInputSchema, StoreGetSettingsInputSchema, StoreUpdateSettingsInputSchema, } from "./schemas.js";
|
|
7
|
+
import { storeList, storeCreate, storeGet, storeUpdate, storeGetSettings, storeUpdateSettings, } from "./handlers.js";
|
|
8
8
|
// Tool definitions
|
|
9
9
|
export const storeToolDefinitions = [
|
|
10
10
|
{
|
|
@@ -43,4 +43,27 @@ export const storeToolDefinitions = [
|
|
|
43
43
|
inputSchema: StoreUpdateInputSchema,
|
|
44
44
|
handler: storeUpdate,
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: "ol_store_get_settings",
|
|
48
|
+
title: "Get Store Settings",
|
|
49
|
+
description: "Get the loyalty program settings for the current store. Returns configuration such as tier assignment type, " +
|
|
50
|
+
"wallet codes, downgrade mode, account activation requirements, member identification method, timezone, " +
|
|
51
|
+
"notification settings, and active member criteria. Use before ol_store_update_settings to see current values.",
|
|
52
|
+
readOnly: true,
|
|
53
|
+
idempotent: true,
|
|
54
|
+
inputSchema: StoreGetSettingsInputSchema,
|
|
55
|
+
handler: storeGetSettings,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "ol_store_update_settings",
|
|
59
|
+
title: "Update Store Settings",
|
|
60
|
+
description: "Update loyalty program settings via PATCH (partial update — only provided fields are changed). " +
|
|
61
|
+
"KEY SETTINGS: tierAssignType ('points'|'transactions'), tierWalletCode, levelDowngradeMode ('none'|'automatic'|'after_x_days'), " +
|
|
62
|
+
"accountActivationRequired, identificationMethod ('email'|'phone'|'loyaltyCardNumber'), timezone. " +
|
|
63
|
+
"Use ol_store_get_settings first to see current values before making changes.",
|
|
64
|
+
readOnly: false,
|
|
65
|
+
idempotent: true,
|
|
66
|
+
inputSchema: StoreUpdateSettingsInputSchema,
|
|
67
|
+
handler: storeUpdateSettings,
|
|
68
|
+
},
|
|
46
69
|
];
|
|
@@ -36,3 +36,27 @@ export declare const StoreUpdateInputSchema: {
|
|
|
36
36
|
active: z.ZodOptional<z.ZodBoolean>;
|
|
37
37
|
currency: z.ZodOptional<z.ZodString>;
|
|
38
38
|
};
|
|
39
|
+
export declare const StoreGetSettingsInputSchema: {
|
|
40
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
41
|
+
};
|
|
42
|
+
export declare const StoreUpdateSettingsInputSchema: {
|
|
43
|
+
storeCode: z.ZodOptional<z.ZodString>;
|
|
44
|
+
programName: z.ZodOptional<z.ZodString>;
|
|
45
|
+
allowCustomersProfileEdits: z.ZodOptional<z.ZodBoolean>;
|
|
46
|
+
tierAssignType: z.ZodOptional<z.ZodEnum<["points", "transactions"]>>;
|
|
47
|
+
tierWalletCode: z.ZodOptional<z.ZodString>;
|
|
48
|
+
excludeDeliveryCostsFromTierAssignment: z.ZodOptional<z.ZodBoolean>;
|
|
49
|
+
excludedLevelCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
50
|
+
levelDowngradeMode: z.ZodOptional<z.ZodEnum<["none", "automatic", "after_x_days"]>>;
|
|
51
|
+
levelDowngradeDays: z.ZodOptional<z.ZodNumber>;
|
|
52
|
+
levelDowngradeBase: z.ZodOptional<z.ZodEnum<["none", "active_points", "earned_points", "earned_points_since_last_level_change"]>>;
|
|
53
|
+
levelResetPointsOnDowngrade: z.ZodOptional<z.ZodBoolean>;
|
|
54
|
+
accountActivationRequired: z.ZodOptional<z.ZodBoolean>;
|
|
55
|
+
identificationMethod: z.ZodOptional<z.ZodEnum<["email", "phone", "loyaltyCardNumber"]>>;
|
|
56
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
57
|
+
timezoneStrategy: z.ZodOptional<z.ZodEnum<["none", "default", "localtime"]>>;
|
|
58
|
+
rewardWalletCode: z.ZodOptional<z.ZodString>;
|
|
59
|
+
expirePointsNotificationDays: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
expireCouponsNotificationDays: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
expireLevelsNotificationDays: z.ZodOptional<z.ZodNumber>;
|
|
62
|
+
};
|
|
@@ -21,3 +21,27 @@ export const StoreUpdateInputSchema = {
|
|
|
21
21
|
active: z.boolean().optional().describe("Whether the store is active."),
|
|
22
22
|
currency: z.string().optional().describe("Currency code."),
|
|
23
23
|
};
|
|
24
|
+
export const StoreGetSettingsInputSchema = {
|
|
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
|
+
};
|
|
27
|
+
export const StoreUpdateSettingsInputSchema = {
|
|
28
|
+
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."),
|
|
29
|
+
programName: z.string().optional().describe("Program/loyalty program name."),
|
|
30
|
+
allowCustomersProfileEdits: z.boolean().optional().describe("Whether members can edit their own profiles."),
|
|
31
|
+
tierAssignType: z.enum(["points", "transactions"]).optional().describe("How tier assignment is calculated: 'points' (based on wallet balance) or 'transactions' (based on purchase count)."),
|
|
32
|
+
tierWalletCode: z.string().optional().describe("Wallet used for tier assignment (wallet type code)."),
|
|
33
|
+
excludeDeliveryCostsFromTierAssignment: z.boolean().optional().describe("Exclude delivery costs from tier calculations."),
|
|
34
|
+
excludedLevelCategories: z.array(z.string()).optional().describe("Product categories excluded from tier assignment."),
|
|
35
|
+
levelDowngradeMode: z.enum(["none", "automatic", "after_x_days"]).optional().describe("Tier downgrade mode: 'none' (never downgrade), 'automatic' (downgrade when criteria unmet), 'after_x_days' (downgrade after N inactive days)."),
|
|
36
|
+
levelDowngradeDays: z.number().optional().describe("Days before tier downgrade (used with levelDowngradeMode: 'after_x_days')."),
|
|
37
|
+
levelDowngradeBase: z.enum(["none", "active_points", "earned_points", "earned_points_since_last_level_change"]).optional().describe("Point basis for downgrade recalculation."),
|
|
38
|
+
levelResetPointsOnDowngrade: z.boolean().optional().describe("Reset points when member is downgraded."),
|
|
39
|
+
accountActivationRequired: z.boolean().optional().describe("Whether new accounts require email activation before participating."),
|
|
40
|
+
identificationMethod: z.enum(["email", "phone", "loyaltyCardNumber"]).optional().describe("Primary member identification method."),
|
|
41
|
+
timezone: z.string().optional().describe("Store timezone (e.g., 'Europe/Warsaw', 'America/New_York')."),
|
|
42
|
+
timezoneStrategy: z.enum(["none", "default", "localtime"]).optional().describe("Timezone handling strategy."),
|
|
43
|
+
rewardWalletCode: z.string().optional().describe("Wallet used for reward purchases (wallet type code)."),
|
|
44
|
+
expirePointsNotificationDays: z.number().optional().describe("Days before point expiry to send notification email."),
|
|
45
|
+
expireCouponsNotificationDays: z.number().optional().describe("Days before coupon expiry to send notification email."),
|
|
46
|
+
expireLevelsNotificationDays: z.number().optional().describe("Days before tier expiry to send notification email."),
|
|
47
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-loyalty/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for Open Loyalty API - enables AI agents to manage loyalty programs, members, points, rewards, and transactions",
|
|
6
6
|
"author": "Marcin Dyguda <md@openloyalty.io>",
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"main": "dist/index.js",
|
|
19
19
|
"bin": {
|
|
20
|
-
"openloyalty-mcp": "./dist/index.js"
|
|
21
|
-
"openloyalty-mcp-http": "./dist/http.js"
|
|
20
|
+
"openloyalty-mcp": "./dist/index.js"
|
|
22
21
|
},
|
|
23
22
|
"files": [
|
|
24
23
|
"dist",
|
|
@@ -31,9 +30,7 @@
|
|
|
31
30
|
"dev:ui": "node scripts/build-ui.mjs --watch",
|
|
32
31
|
"prepublishOnly": "npm run build",
|
|
33
32
|
"start": "node dist/index.js",
|
|
34
|
-
"start:http": "node dist/http.js",
|
|
35
33
|
"dev": "tsx src/index.ts",
|
|
36
|
-
"dev:http": "tsx src/http.ts",
|
|
37
34
|
"typecheck": "tsc --noEmit",
|
|
38
35
|
"test": "vitest",
|
|
39
36
|
"test:run": "vitest run",
|
|
@@ -47,19 +44,12 @@
|
|
|
47
44
|
"@modelcontextprotocol/ext-apps": "^1.0.1",
|
|
48
45
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
49
46
|
"axios": "^1.6.0",
|
|
50
|
-
"cors": "^2.8.5",
|
|
51
47
|
"dotenv": "^17.2.3",
|
|
52
|
-
"express": "^5.2.1",
|
|
53
|
-
"express-rate-limit": "^8.2.1",
|
|
54
48
|
"form-data": "^4.0.0",
|
|
55
|
-
"helmet": "^8.1.0",
|
|
56
|
-
"ioredis": "^5.9.2",
|
|
57
49
|
"zod": "^3.22.0"
|
|
58
50
|
},
|
|
59
51
|
"devDependencies": {
|
|
60
52
|
"@eslint/js": "^9.39.2",
|
|
61
|
-
"@types/cors": "^2.8.19",
|
|
62
|
-
"@types/express": "^5.0.6",
|
|
63
53
|
"@types/node": "^20.10.0",
|
|
64
54
|
"@vitest/coverage-v8": "^4.0.17",
|
|
65
55
|
"axios-mock-adapter": "^2.1.0",
|
package/dist/auth/provider.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { OAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/provider.js";
|
|
2
|
-
/**
|
|
3
|
-
* Open Loyalty API credentials stored per-client
|
|
4
|
-
*/
|
|
5
|
-
export interface OpenLoyaltyConfig {
|
|
6
|
-
apiUrl: string;
|
|
7
|
-
apiToken: string;
|
|
8
|
-
storeCode: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Creates the OAuth server provider
|
|
12
|
-
*/
|
|
13
|
-
export declare function createOAuthProvider(issuerUrl: string): OAuthServerProvider;
|
|
14
|
-
/**
|
|
15
|
-
* Completes authorization after form submission
|
|
16
|
-
*/
|
|
17
|
-
export declare function completeAuthorization(sessionId: string, config: OpenLoyaltyConfig): Promise<{
|
|
18
|
-
redirectUrl: string;
|
|
19
|
-
} | {
|
|
20
|
-
error: string;
|
|
21
|
-
}>;
|
|
22
|
-
/**
|
|
23
|
-
* Gets the Open Loyalty config for a client
|
|
24
|
-
*/
|
|
25
|
-
export declare function getClientConfig(clientId: string): Promise<OpenLoyaltyConfig | undefined>;
|
|
26
|
-
/**
|
|
27
|
-
* Validates Open Loyalty credentials
|
|
28
|
-
* Uses the member list endpoint to validate both API token and store code
|
|
29
|
-
*/
|
|
30
|
-
export declare function validateOpenLoyaltyCredentials(config: OpenLoyaltyConfig): Promise<{
|
|
31
|
-
valid: boolean;
|
|
32
|
-
error?: string;
|
|
33
|
-
}>;
|