@open-loyalty/mcp-server 1.0.3 → 1.3.1

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 (142) hide show
  1. package/README.md +180 -177
  2. package/dist/auth/provider.js +2 -14
  3. package/dist/auth/storage.js +22 -0
  4. package/dist/client/http.d.ts +5 -0
  5. package/dist/client/http.js +62 -3
  6. package/dist/config.d.ts +6 -5
  7. package/dist/config.js +15 -11
  8. package/dist/http.js +170 -65
  9. package/dist/instructions.d.ts +5 -0
  10. package/dist/instructions.js +420 -0
  11. package/dist/prompts/fan-engagement-setup.d.ts +107 -0
  12. package/dist/prompts/fan-engagement-setup.js +492 -0
  13. package/dist/server.d.ts +1 -1
  14. package/dist/server.js +68 -278
  15. package/dist/tools/achievement/handlers.d.ts +117 -0
  16. package/dist/tools/achievement/handlers.js +161 -0
  17. package/dist/tools/achievement/index.d.ts +479 -0
  18. package/dist/tools/achievement/index.js +74 -0
  19. package/dist/tools/achievement/schemas.d.ts +433 -0
  20. package/dist/tools/achievement/schemas.js +142 -0
  21. package/dist/tools/achievement.d.ts +155 -121
  22. package/dist/tools/achievement.js +82 -39
  23. package/dist/tools/admin.d.ts +18 -6
  24. package/dist/tools/admin.js +24 -12
  25. package/dist/tools/analytics.d.ts +29 -11
  26. package/dist/tools/analytics.js +58 -48
  27. package/dist/tools/apikey.d.ts +10 -3
  28. package/dist/tools/apikey.js +13 -6
  29. package/dist/tools/audit.d.ts +6 -2
  30. package/dist/tools/audit.js +8 -4
  31. package/dist/tools/badge.d.ts +14 -6
  32. package/dist/tools/badge.js +36 -27
  33. package/dist/tools/campaign/handlers.d.ts +42 -0
  34. package/dist/tools/campaign/handlers.js +223 -0
  35. package/dist/tools/campaign/index.d.ts +783 -0
  36. package/dist/tools/campaign/index.js +112 -0
  37. package/dist/tools/campaign/member-handlers.d.ts +60 -0
  38. package/dist/tools/campaign/member-handlers.js +159 -0
  39. package/dist/tools/campaign/schemas.d.ts +704 -0
  40. package/dist/tools/campaign/schemas.js +259 -0
  41. package/dist/tools/campaign/types.d.ts +161 -0
  42. package/dist/tools/campaign/types.js +2 -0
  43. package/dist/tools/campaign.d.ts +41 -16
  44. package/dist/tools/campaign.js +38 -25
  45. package/dist/tools/custom-event.d.ts +315 -0
  46. package/dist/tools/custom-event.js +270 -0
  47. package/dist/tools/export.d.ts +12 -4
  48. package/dist/tools/export.js +25 -20
  49. package/dist/tools/import.d.ts +9 -3
  50. package/dist/tools/import.js +33 -21
  51. package/dist/tools/index.d.ts +3 -11
  52. package/dist/tools/index.js +17 -475
  53. package/dist/tools/member/handlers.d.ts +111 -0
  54. package/dist/tools/member/handlers.js +206 -0
  55. package/dist/tools/member/index.d.ts +169 -0
  56. package/dist/tools/member/index.js +92 -0
  57. package/dist/tools/member/schemas.d.ts +89 -0
  58. package/dist/tools/member/schemas.js +65 -0
  59. package/dist/tools/member.d.ts +21 -0
  60. package/dist/tools/member.js +56 -62
  61. package/dist/tools/points.d.ts +19 -6
  62. package/dist/tools/points.js +51 -49
  63. package/dist/tools/referral/handlers.d.ts +47 -0
  64. package/dist/tools/referral/handlers.js +115 -0
  65. package/dist/tools/referral/index.d.ts +44 -0
  66. package/dist/tools/referral/index.js +44 -0
  67. package/dist/tools/referral/schemas.d.ts +34 -0
  68. package/dist/tools/referral/schemas.js +52 -0
  69. package/dist/tools/reward/handlers.d.ts +110 -0
  70. package/dist/tools/reward/handlers.js +289 -0
  71. package/dist/tools/reward/index.d.ts +177 -0
  72. package/dist/tools/reward/index.js +90 -0
  73. package/dist/tools/reward/schemas.d.ts +116 -0
  74. package/dist/tools/reward/schemas.js +91 -0
  75. package/dist/tools/reward.d.ts +18 -0
  76. package/dist/tools/reward.js +56 -66
  77. package/dist/tools/role.d.ts +26 -7
  78. package/dist/tools/role.js +25 -12
  79. package/dist/tools/segment/handlers.d.ts +87 -0
  80. package/dist/tools/segment/handlers.js +174 -0
  81. package/dist/tools/segment/index.d.ts +395 -0
  82. package/dist/tools/segment/index.js +87 -0
  83. package/dist/tools/segment/schemas.d.ts +337 -0
  84. package/dist/tools/segment/schemas.js +79 -0
  85. package/dist/tools/segment.d.ts +29 -10
  86. package/dist/tools/segment.js +84 -50
  87. package/dist/tools/store.d.ts +12 -4
  88. package/dist/tools/store.js +16 -8
  89. package/dist/tools/tierset.d.ts +19 -7
  90. package/dist/tools/tierset.js +44 -35
  91. package/dist/tools/transaction.d.ts +16 -8
  92. package/dist/tools/transaction.js +25 -21
  93. package/dist/tools/wallet-type.d.ts +7 -3
  94. package/dist/tools/wallet-type.js +14 -12
  95. package/dist/tools/webhook.d.ts +23 -10
  96. package/dist/tools/webhook.js +135 -33
  97. package/dist/types/schemas/achievement.d.ts +12 -309
  98. package/dist/types/schemas/achievement.js +0 -13
  99. package/dist/types/schemas/admin.d.ts +10 -97
  100. package/dist/types/schemas/admin.js +0 -38
  101. package/dist/types/schemas/badge.d.ts +0 -37
  102. package/dist/types/schemas/badge.js +0 -11
  103. package/dist/types/schemas/campaign.d.ts +64 -832
  104. package/dist/types/schemas/campaign.js +2 -25
  105. package/dist/types/schemas/common.d.ts +5 -0
  106. package/dist/types/schemas/common.js +5 -0
  107. package/dist/types/schemas/export.d.ts +0 -17
  108. package/dist/types/schemas/export.js +0 -7
  109. package/dist/types/schemas/member.d.ts +37 -176
  110. package/dist/types/schemas/member.js +0 -27
  111. package/dist/types/schemas/points.d.ts +0 -63
  112. package/dist/types/schemas/points.js +0 -22
  113. package/dist/types/schemas/reward.d.ts +71 -68
  114. package/dist/types/schemas/reward.js +8 -28
  115. package/dist/types/schemas/role.d.ts +0 -100
  116. package/dist/types/schemas/role.js +0 -29
  117. package/dist/types/schemas/segment.d.ts +0 -58
  118. package/dist/types/schemas/segment.js +0 -17
  119. package/dist/types/schemas/tierset.d.ts +0 -176
  120. package/dist/types/schemas/tierset.js +0 -27
  121. package/dist/types/schemas/transaction.d.ts +23 -254
  122. package/dist/types/schemas/transaction.js +0 -7
  123. package/dist/types/schemas/wallet-type.d.ts +8 -8
  124. package/dist/types/schemas/wallet-type.js +1 -1
  125. package/dist/types/schemas/webhook.d.ts +0 -58
  126. package/dist/types/schemas/webhook.js +0 -12
  127. package/dist/utils/errors.js +30 -3
  128. package/dist/utils/payload.d.ts +12 -0
  129. package/dist/utils/payload.js +14 -0
  130. package/dist/workflows/app-login-streak.d.ts +39 -0
  131. package/dist/workflows/app-login-streak.js +298 -0
  132. package/dist/workflows/early-arrival.d.ts +33 -0
  133. package/dist/workflows/early-arrival.js +148 -0
  134. package/dist/workflows/index.d.ts +101 -0
  135. package/dist/workflows/index.js +208 -0
  136. package/dist/workflows/match-attendance.d.ts +45 -0
  137. package/dist/workflows/match-attendance.js +308 -0
  138. package/dist/workflows/sportsbar-visit.d.ts +41 -0
  139. package/dist/workflows/sportsbar-visit.js +284 -0
  140. package/dist/workflows/vod-watching.d.ts +43 -0
  141. package/dist/workflows/vod-watching.js +326 -0
  142. package/package.json +10 -2
@@ -1,49 +1,143 @@
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
- import { getConfig } from "../config.js";
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
+ }
66
+ // SSRF protection: validate webhook URLs are external HTTPS endpoints
67
+ function isValidWebhookUrl(url) {
68
+ try {
69
+ const parsed = new URL(url);
70
+ // Must be HTTPS
71
+ if (parsed.protocol !== "https:") {
72
+ return false;
73
+ }
74
+ const hostname = parsed.hostname.toLowerCase();
75
+ // Block internal hostnames
76
+ if (isInternalHostname(hostname)) {
77
+ return false;
78
+ }
79
+ // If hostname is an IP address, validate it directly
80
+ if (isIP(hostname)) {
81
+ return !isPrivateIP(hostname);
82
+ }
83
+ // Block cloud metadata IP
84
+ if (hostname === "169.254.169.254") {
85
+ return false;
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.
91
+ return true;
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ }
97
+ const webhookUrlSchema = z.string()
98
+ .url("Must be a valid URL")
99
+ .refine(isValidWebhookUrl, "URL must be an external HTTPS endpoint (no localhost, private IPs, or metadata endpoints)");
5
100
  // Input Schemas
6
101
  export const WebhookListInputSchema = {
7
- storeCode: z.string().optional().describe("Store code. If not provided, uses the default store code from configuration."),
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."),
8
103
  page: z.number().optional().describe("Page number (default: 1)."),
9
104
  perPage: z.number().optional().describe("Items per page (default: 25)."),
10
105
  eventName: z.string().optional().describe("Filter by event name."),
11
106
  url: z.string().optional().describe("Filter by URL."),
12
107
  };
13
108
  export const WebhookCreateInputSchema = {
14
- storeCode: z.string().optional().describe("Store code. If not provided, uses the default store code from configuration."),
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."),
15
110
  eventName: z.string().describe("Event name to subscribe to. Use webhook_events to discover available events."),
16
- url: z.string().describe("URL to receive webhook events."),
111
+ url: webhookUrlSchema.describe("HTTPS URL to receive webhook events. Must be an external endpoint (no localhost or private IPs)."),
17
112
  headers: z.array(z.object({
18
113
  headerName: z.string().describe("Header name."),
19
114
  headerValue: z.string().describe("Header value."),
20
115
  })).optional().describe("Custom headers to include in webhook requests."),
21
116
  };
22
117
  export const WebhookGetInputSchema = {
23
- storeCode: z.string().optional().describe("Store code. If not provided, uses the default store code from configuration."),
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."),
24
119
  webhookSubscriptionId: z.string().describe("The webhook subscription ID (UUID) to retrieve."),
25
120
  };
26
121
  export const WebhookUpdateInputSchema = {
27
- storeCode: z.string().optional().describe("Store code. If not provided, uses the default store code from configuration."),
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."),
28
123
  webhookSubscriptionId: z.string().describe("The webhook subscription ID (UUID) to update."),
29
124
  eventName: z.string().optional().describe("Event name to subscribe to."),
30
- url: z.string().optional().describe("URL to receive webhook events."),
125
+ url: webhookUrlSchema.optional().describe("HTTPS URL to receive webhook events. Must be an external endpoint (no localhost or private IPs)."),
31
126
  headers: z.array(z.object({
32
127
  headerName: z.string().describe("Header name."),
33
128
  headerValue: z.string().describe("Header value."),
34
129
  })).optional().describe("Custom headers to include in webhook requests."),
35
130
  };
36
131
  export const WebhookDeleteInputSchema = {
37
- storeCode: z.string().optional().describe("Store code. If not provided, uses the default store code from configuration."),
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."),
38
133
  webhookSubscriptionId: z.string().describe("The webhook subscription ID (UUID) to delete."),
39
134
  };
40
135
  export const WebhookEventsInputSchema = {
41
- storeCode: z.string().optional().describe("Store code. If not provided, uses the default store code from configuration."),
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."),
42
137
  };
43
138
  // Handler functions
44
139
  export async function webhookList(input) {
45
- const config = getConfig();
46
- const storeCode = input.storeCode || config.defaultStoreCode;
140
+ const storeCode = getStoreCode(input.storeCode);
47
141
  const params = new URLSearchParams();
48
142
  if (input.page)
49
143
  params.append("_page", String(input.page));
@@ -60,12 +154,11 @@ export async function webhookList(input) {
60
154
  return response;
61
155
  }
62
156
  catch (error) {
63
- throw formatApiError(error, "openloyalty_webhook_list");
157
+ throw formatApiError(error, "ol_webhook_list");
64
158
  }
65
159
  }
66
160
  export async function webhookCreate(input) {
67
- const config = getConfig();
68
- const storeCode = input.storeCode || config.defaultStoreCode;
161
+ const storeCode = getStoreCode(input.storeCode);
69
162
  const payload = {
70
163
  eventName: input.eventName,
71
164
  url: input.url,
@@ -78,23 +171,21 @@ export async function webhookCreate(input) {
78
171
  return response;
79
172
  }
80
173
  catch (error) {
81
- throw formatApiError(error, "openloyalty_webhook_create");
174
+ throw formatApiError(error, "ol_webhook_create");
82
175
  }
83
176
  }
84
177
  export async function webhookGet(input) {
85
- const config = getConfig();
86
- const storeCode = input.storeCode || config.defaultStoreCode;
178
+ const storeCode = getStoreCode(input.storeCode);
87
179
  try {
88
180
  const response = await apiGet(`/${storeCode}/webhook/subscription/${input.webhookSubscriptionId}`);
89
181
  return response;
90
182
  }
91
183
  catch (error) {
92
- throw formatApiError(error, "openloyalty_webhook_get");
184
+ throw formatApiError(error, "ol_webhook_get");
93
185
  }
94
186
  }
95
187
  export async function webhookUpdate(input) {
96
- const config = getConfig();
97
- const storeCode = input.storeCode || config.defaultStoreCode;
188
+ const storeCode = getStoreCode(input.storeCode);
98
189
  const payload = {};
99
190
  if (input.eventName)
100
191
  payload.eventName = input.eventName;
@@ -106,65 +197,76 @@ export async function webhookUpdate(input) {
106
197
  await apiPut(`/${storeCode}/webhook/subscription/${input.webhookSubscriptionId}`, { webhookSubscription: payload });
107
198
  }
108
199
  catch (error) {
109
- throw formatApiError(error, "openloyalty_webhook_update");
200
+ throw formatApiError(error, "ol_webhook_update");
110
201
  }
111
202
  }
112
203
  export async function webhookDelete(input) {
113
- const config = getConfig();
114
- const storeCode = input.storeCode || config.defaultStoreCode;
204
+ const storeCode = getStoreCode(input.storeCode);
115
205
  try {
116
206
  await apiDelete(`/${storeCode}/webhook/subscription/${input.webhookSubscriptionId}`);
117
207
  }
118
208
  catch (error) {
119
- throw formatApiError(error, "openloyalty_webhook_delete");
209
+ throw formatApiError(error, "ol_webhook_delete");
120
210
  }
121
211
  }
122
212
  export async function webhookEvents(input) {
123
- const config = getConfig();
124
- const storeCode = input.storeCode || config.defaultStoreCode;
213
+ const storeCode = getStoreCode(input.storeCode);
125
214
  try {
126
215
  const response = await apiGet(`/${storeCode}/webhook/event`);
127
216
  return response;
128
217
  }
129
218
  catch (error) {
130
- throw formatApiError(error, "openloyalty_webhook_events");
219
+ throw formatApiError(error, "ol_webhook_events");
131
220
  }
132
221
  }
133
222
  // Tool definitions
134
223
  export const webhookToolDefinitions = [
135
224
  {
136
- name: "openloyalty_webhook_list",
225
+ name: "ol_webhook_list",
226
+ title: "List Webhook Subscriptions",
137
227
  description: "List webhook subscriptions with optional filtering. Returns paginated list of subscriptions with webhookSubscriptionId, eventName, url, and createdAt.",
228
+ readOnly: true,
138
229
  inputSchema: WebhookListInputSchema,
139
230
  handler: webhookList,
140
231
  },
141
232
  {
142
- name: "openloyalty_webhook_create",
233
+ name: "ol_webhook_create",
234
+ title: "Create Webhook Subscription",
143
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.",
236
+ readOnly: false,
144
237
  inputSchema: WebhookCreateInputSchema,
145
238
  handler: webhookCreate,
146
239
  },
147
240
  {
148
- name: "openloyalty_webhook_get",
241
+ name: "ol_webhook_get",
242
+ title: "Get Webhook Subscription Details",
149
243
  description: "Get full webhook subscription details including headers configuration.",
244
+ readOnly: true,
150
245
  inputSchema: WebhookGetInputSchema,
151
246
  handler: webhookGet,
152
247
  },
153
248
  {
154
- name: "openloyalty_webhook_update",
249
+ name: "ol_webhook_update",
250
+ title: "Update Webhook Subscription",
155
251
  description: "Update a webhook subscription. Can update eventName, url, and headers. Returns void on success (204 No Content).",
252
+ readOnly: false,
156
253
  inputSchema: WebhookUpdateInputSchema,
157
254
  handler: webhookUpdate,
158
255
  },
159
256
  {
160
- name: "openloyalty_webhook_delete",
257
+ name: "ol_webhook_delete",
258
+ title: "Delete Webhook Subscription (Permanent)",
161
259
  description: "Delete a webhook subscription. Returns void on success (204 No Content). The subscription will stop receiving events immediately.",
260
+ readOnly: false,
261
+ destructive: true,
162
262
  inputSchema: WebhookDeleteInputSchema,
163
263
  handler: webhookDelete,
164
264
  },
165
265
  {
166
- name: "openloyalty_webhook_events",
266
+ name: "ol_webhook_events",
267
+ title: "List Available Webhook Events",
167
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.",
269
+ readOnly: true,
168
270
  inputSchema: WebhookEventsInputSchema,
169
271
  handler: webhookEvents,
170
272
  },