@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
package/dist/server.js CHANGED
@@ -1,290 +1,57 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { getAllTools, getToolHandler, toolMetadata } from "./tools/index.js";
2
+ import { z } from "zod";
3
+ import { getAllTools, getToolHandler } from "./tools/index.js";
3
4
  import { OpenLoyaltyError } from "./utils/errors.js";
4
- const SERVER_INSTRUCTIONS = `
5
- Open Loyalty MCP Server - Complete Loyalty Program Management
6
-
7
- ## Domain Model
8
-
9
- Member (loyalty program participant)
10
- └── Points (wallet balance, transfers)
11
- └── Tier (current level: Bronze, Silver, Gold)
12
- └── Transactions (purchase history)
13
- └── Rewards (redeemed coupons)
14
- └── Achievements (gamification progress)
15
- └── Badges (visual rewards from achievements)
16
-
17
- TierSet (loyalty program structure)
18
- └── Conditions (criteria: activeUnits, totalSpending)
19
- └── Tiers (levels with thresholds)
20
-
21
- WalletType (points currency configuration)
22
-
23
- Reward (redeemable items)
24
- └── Categories (reward groupings)
25
-
26
- Campaign (automated points/rewards rules)
27
- └── Type (earning, spending, custom, instant_reward)
28
- └── Trigger (transaction, custom_event, points_transfer)
29
- └── Rules (SKU, category, transaction amount conditions)
30
- └── Effects (give_points, multiply_points, percentage_discount)
31
- └── Targeting (segments, tiers for audience)
32
-
33
- Segment (member audience grouping)
34
- └── Parts (groups of criteria - OR logic between parts)
35
- └── Criteria (conditions - AND logic within part)
36
- Types: purchase_period, transaction_count, average_transaction,
37
- anniversary, last_purchase_days, tier
38
-
39
- Achievement (gamification goals)
40
- └── Trigger (transaction, custom_event, points_transfer, referral, login)
41
- └── CompleteRule (periodGoal, period type, unique counting)
42
- └── Badge (visual reward when completed)
43
-
44
- Badge (visual recognition)
45
- └── Linked to achievements via badgeTypeId
46
- └── Auto-created when referenced by achievement
47
-
48
- ## Key Workflows
49
-
50
- ### 1. Create 3-tier Loyalty Program:
51
- wallet_type_list → tierset_create → tierset_get → tierset_update_tiers
52
-
53
- ### 2. Register and Reward Member:
54
- member_create → points_add (welcome bonus) → member_get (verify)
55
-
56
- ### 3. Record Purchase and Earn Points:
57
- transaction_create (with customerData) → triggers campaigns → points auto-added
58
-
59
- ### 4. Redeem Reward:
60
- reward_list → reward_buy (deducts points) → reward_redeem (mark coupon used)
61
-
62
- ### 5. Assign Unmatched Transaction:
63
- transaction_list (matched=false) → transaction_assign_member → points earned
64
-
65
- ### 6. Check Member Tier Progress:
66
- member_get_tier_progress → shows current tier, next tier, progress %
67
-
68
- ### 7. Double Points for VIP Members:
69
- segment_create (with tier criteria for VIP level) → campaign_create (targeting that segment with give_points effect and multiplier: 2)
70
-
71
- ### 8. Purchase Achievement with Badge:
72
- badge_list (find or note badgeTypeId) → achievement_create (trigger: transaction, periodGoal for amount, badgeTypeId)
73
-
74
- ### 9. Create Targeted Promotion:
75
- segment_create (define audience) → campaign_create (type: earning, target: segment, segmentIds: [segmentId])
76
-
77
- ### 10. Track Member Gamification:
78
- achievement_list_member_achievements → badge_get_member_badges → achievement_get_member_progress
79
-
80
- ## Discovery Paths:
81
- - Members: member_list → member_get → points_get_balance
82
- - Tiers: tierset_list → tierset_get → tierset_get_tiers
83
- - Rewards: reward_category_list → reward_list → reward_get
84
- - Transactions: transaction_list → transaction_get
85
- - Campaigns: campaign_list → campaign_get → campaign_simulate
86
- - Segments: segment_list → segment_get → segment_get_members → campaign_create (audience targeting)
87
- - Achievements: achievement_list → badge_list → achievement_create (with badge)
88
- - Member Gamification: achievement_list_member_achievements → badge_get_member_badges
89
-
90
- ## Important Patterns:
91
- - conditionId REQUIRED for tier thresholds - call tierset_get after tierset_create
92
- - Points operations: check balance with points_get_balance before spending
93
- - Reward flow: reward_buy returns couponCode, use reward_redeem to mark used
94
- - Transactions auto-match to members via customerData (email, phone, loyaltyCardNumber)
95
-
96
- ### Pagination:
97
- - Traditional pagination: use page + perPage params
98
- - Cursor pagination: provide cursor from previous response for efficient deep pagination
99
- - Cursor-enabled tools: member_list, transaction_list, points_get_history
100
- - First scroll request: cursor="" (empty string)
101
- - Subsequent requests: use cursor value from previous response
102
- - When cursor provided, page param is ignored
103
-
104
- ### Campaign Patterns:
105
- - Campaign types: earning (earn points), spending (spend points), custom (custom events), instant_reward
106
- - Triggers: transaction (purchase), custom_event, points_transfer, referral, registration
107
- - Effects: give_points, multiply_points, percentage_discount, fixed_discount
108
- - Target campaigns to segments or tiers using target: "segment" and segmentIds array
109
-
110
- ### Segment Patterns:
111
- - Parts use OR logic (member matches ANY part)
112
- - Criteria within a part use AND logic (member must match ALL criteria in that part)
113
- - Common criteria: purchase_period (spending), transaction_count, average_transaction, anniversary, last_purchase_days, tier
114
-
115
- ### Achievement Patterns:
116
- - Triggers: transaction, custom_event, points_transfer, reward_redemption, referral, achievement, tier_change, registration, profile_update, login
117
- - periodGoal sets the target (e.g., 5 purchases, 1000 points spent)
118
- - period defines timeframe: day, week, month, year, forever
119
- - Link to badge via badgeTypeId - badge auto-created if doesn't exist
120
- - uniqueAttribute for counting distinct values (e.g., unique products)
121
-
122
- ## Phase 3: Analytics, Admin, Roles & Stores
123
-
124
- ### Domain Model (Extended)
125
-
126
- Admin (system user)
127
- └── Roles (permission sets)
128
- └── Permissions (resource + access level)
129
- └── API Keys (programmatic access)
130
-
131
- Store (multi-tenant container)
132
- └── Members, Campaigns, Rewards (isolated per store)
133
-
134
- Audit Log (compliance tracking)
135
- └── eventType, entityType, entityId, username, timestamp
136
-
137
- ### Analytics Workflows:
138
-
139
- #### 11. Get Program Overview:
140
- analytics_dashboard → analytics_tiers → analytics_members
141
-
142
- #### 12. Analyze Points Economy:
143
- analytics_points → analytics_units (per wallet) → points_get_histogram
144
-
145
- #### 13. Measure Campaign Performance:
146
- analytics_campaigns → analytics_campaign_detail (specific campaign)
147
-
148
- #### 14. Track Transactions:
149
- analytics_transactions → transaction_list (details)
150
-
151
- ### Aggregation Queries (Top Spenders, Purchase Analysis):
152
-
153
- IMPORTANT: For queries like "top 5 spenders in July 2025", use this approach:
154
-
155
- #### 15. Find Top Spenders by Date Range:
156
- 1. transaction_list(purchasedAtFrom, purchasedAtTo, perPage=50, cursor='') - returns customerId with each transaction
157
- 2. Use cursor pagination to fetch ALL pages - even if there are 1000+ transactions
158
- 3. Aggregate grossValue by customerId in your code
159
- 4. Sort by total spent, take top N
160
- 5. member_get for each top spender to get names/details
161
-
162
- CRITICAL - DO NOT TRY TO BE SMART OR OPTIMIZE:
163
- - ALWAYS iterate through ALL pages using cursor pagination - this is the ONLY correct approach
164
- - DO NOT skip pages or try to sample data - you will get inaccurate results
165
- - DO NOT use transaction_get individually - transaction_list already includes customerId
166
- - DO NOT try to find "smarter" analytics endpoints - they don't exist for per-customer aggregation
167
- - Large datasets (1000+ transactions) are normal - just keep paginating until cursor is empty
168
- - Start with cursor='' (empty string), use returned cursor for next request, repeat until done
169
-
170
- Example: Top 5 spenders July 2025
171
- - First request: transaction_list(purchasedAtFrom: "2025-07-01", purchasedAtTo: "2025-07-31", perPage: 50, cursor: "")
172
- - Each result includes: transactionId, grossValue, customerId, purchasedAt
173
- - Keep fetching with returned cursor until response has no cursor or empty transactions
174
- - Even if there are 1500 transactions (30 pages), iterate through ALL of them
175
- - Group by customerId, sum grossValue, sort descending, take 5
176
-
177
- ### Admin & Security Workflows:
178
-
179
- #### 15. Create Admin with Limited Role:
180
- acl_get_resources → role_create → admin_create (with roleId)
181
-
182
- #### 16. Generate API Key for Integration:
183
- admin_get → apikey_create → SAVE TOKEN IMMEDIATELY (shown once!)
184
-
185
- #### 17. Audit User Actions:
186
- audit_list (filter by username/entity) → audit_export (compliance report)
187
-
188
- ### Store Multi-Tenancy:
189
-
190
- #### 18. Create New Store:
191
- store_create → configure campaigns/tiers → member_create (with storeCode)
192
-
193
- ### Analytics Query Patterns:
194
- - All analytics tools support dateFrom/dateTo ISO date filters
195
- - analytics_dashboard: high-level program metrics
196
- - analytics_units: wallet-specific metrics (requires walletTypeCode)
197
- - analytics_campaign_detail: detailed metrics for single campaign
198
-
199
- ### Admin → Role → Permission Model:
200
- ┌─────────┐ ┌──────┐ ┌────────────┐
201
- │ Admin │────▶│ Role │────▶│ Permission │
202
- └─────────┘ └──────┘ └────────────┘
203
- │ │
204
- │ resource + access
205
- │ (VIEW, MODIFY, etc.)
206
-
207
- ┌─────────┐
208
- │ API Key │
209
- └─────────┘
210
-
211
- ### Store Multi-Tenancy:
212
- - Each store is isolated: members, campaigns, rewards, transactions
213
- - storeCode parameter routes requests to correct tenant
214
- - Default store used when storeCode omitted
215
-
216
- ## Phase 4: Webhooks, Import & Export
217
-
218
- ### Domain Model (Extended)
219
-
220
- Webhook Subscription (event notification)
221
- └── eventName (event to subscribe to)
222
- └── url (callback endpoint)
223
- └── headers (custom HTTP headers)
224
-
225
- Import (bulk data upload)
226
- └── type: member, groupValue, segmentMembers, unitTransferAdding, etc.
227
- └── status: pending, processing, succeed, failed
228
- └── items (individual row results)
229
-
230
- Export (bulk data download)
231
- └── type: campaignCode, member, memberTier, memberSegment, rewardFulfillment
232
- └── status: pending, done, failed, error
233
- └── CSV file (when status='done')
234
-
235
- ### Webhook Workflows:
236
-
237
- #### 19. Subscribe to Member Events for CRM Sync:
238
- webhook_events → webhook_create (eventName: 'member.created', url: 'https://crm.example.com/webhook')
239
-
240
- #### 20. List and Manage Subscriptions:
241
- webhook_list → webhook_get → webhook_update or webhook_delete
242
-
243
- ### Import Workflows:
244
-
245
- #### 21. Bulk Import Members from CSV:
246
- import_create (type: 'member', fileContent: CSV data) → import_list → import_get (check status)
247
-
248
- #### 22. Bulk Add Points to Members:
249
- import_create (type: 'unitTransferAdding', fileContent: CSV) → poll import_get until complete
250
-
251
- ### Export Workflows:
252
-
253
- #### 23. Export Campaign Codes:
254
- export_create (type: 'campaignCode', filters: { campaignId }) → poll export_get (until status='done') → export_download
255
-
256
- #### 24. Export Member Data:
257
- export_create (type: 'member') → export_get → export_download (returns CSV)
258
-
259
- ### Webhook Patterns:
260
- - Use webhook_events to discover available event types before subscribing
261
- - API uses wrapper: { webhookSubscription: { eventName, url, headers? } }
262
- - Common events: member.created, member.updated, transaction.created, reward.purchased
263
-
264
- ### Import Patterns:
265
- - Import is async: create returns importId, poll status with import_get
266
- - CSV format required - provide plain text, not base64
267
- - Types: member, groupValue, segmentMembers, unitTransferAdding, unitTransferSpending, transaction, campaignCode, rewardCoupon
268
-
269
- ### Export Patterns:
270
- - Export is async: create returns exportId, poll status until 'done'
271
- - API body wrapper varies by type: { campaignCode: { filters... } }
272
- - Only call export_download when status='done'
273
- - Types: campaignCode, member, memberTier, memberSegment, rewardFulfillment
274
- `;
5
+ import { SERVER_INSTRUCTIONS } from "./instructions.js";
6
+ /**
7
+ * Convert an inputSchema object (Record<string, ZodType>) to a Zod object schema.
8
+ * This allows centralized validation of all tool inputs.
9
+ */
10
+ function createZodSchema(inputSchema) {
11
+ return z.object(inputSchema);
12
+ }
13
+ const TOOL_WRAPPER_KEYS = {
14
+ ol_tierset_create: "tierSet",
15
+ ol_tierset_update: "tierSet",
16
+ ol_campaign_create: "campaign",
17
+ ol_campaign_update: "campaign",
18
+ };
19
+ function normalizeToolInput(toolName, args) {
20
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
21
+ return args;
22
+ }
23
+ const wrapperKey = TOOL_WRAPPER_KEYS[toolName];
24
+ if (!wrapperKey) {
25
+ return args;
26
+ }
27
+ const record = args;
28
+ const wrapped = record[wrapperKey];
29
+ if (!wrapped || typeof wrapped !== "object" || Array.isArray(wrapped)) {
30
+ return args;
31
+ }
32
+ const { [wrapperKey]: _wrapper, ...rest } = record;
33
+ const merged = {
34
+ ...rest,
35
+ ...wrapped,
36
+ };
37
+ return merged;
38
+ }
275
39
  export function createServer() {
276
40
  const server = new McpServer({
277
41
  name: "openloyalty",
278
- version: "1.0.0",
42
+ version: "1.3.0",
279
43
  });
280
44
  const tools = getAllTools();
281
45
  for (const tool of tools) {
282
- const metadata = toolMetadata[tool.name];
283
46
  server.registerTool(tool.name, {
284
- title: metadata?.title,
47
+ title: tool.title,
285
48
  description: tool.description,
286
49
  inputSchema: tool.inputSchema,
287
- annotations: metadata?.annotations,
50
+ annotations: {
51
+ readOnlyHint: tool.readOnly,
52
+ destructiveHint: tool.destructive,
53
+ openWorldHint: true,
54
+ },
288
55
  }, async (args) => {
289
56
  const handler = getToolHandler(tool.name);
290
57
  if (!handler) {
@@ -299,12 +66,35 @@ export function createServer() {
299
66
  };
300
67
  }
301
68
  try {
302
- const result = await handler(args);
69
+ // Validate input against the tool's Zod schema
70
+ const schema = createZodSchema(tool.inputSchema);
71
+ let parseResult = schema.safeParse(args);
72
+ if (!parseResult.success) {
73
+ const normalizedArgs = normalizeToolInput(tool.name, args);
74
+ if (normalizedArgs !== args) {
75
+ const retryResult = schema.safeParse(normalizedArgs);
76
+ if (retryResult.success) {
77
+ parseResult = retryResult;
78
+ }
79
+ }
80
+ }
81
+ if (!parseResult.success) {
82
+ const issues = parseResult.error.issues
83
+ .map((i) => `${i.path.join(".") || "input"}: ${i.message}`)
84
+ .join("; ");
85
+ throw new OpenLoyaltyError({
86
+ code: "VALIDATION_ERROR",
87
+ message: "Invalid input parameters",
88
+ hint: `Fix the following validation errors: ${issues}`,
89
+ relatedTool: tool.name,
90
+ });
91
+ }
92
+ const result = await handler(parseResult.data);
303
93
  return {
304
94
  content: [
305
95
  {
306
96
  type: "text",
307
- text: result === undefined ? "Success" : JSON.stringify(result, null, 2),
97
+ text: result === undefined ? "Success" : JSON.stringify(result, null, process.env.MCP_DEBUG === "true" ? 2 : undefined),
308
98
  },
309
99
  ],
310
100
  };
@@ -0,0 +1,117 @@
1
+ import { AchievementListItem } from "../../types/schemas/achievement.js";
2
+ export declare function achievementList(input: {
3
+ storeCode?: string;
4
+ page?: number;
5
+ perPage?: number;
6
+ active?: boolean;
7
+ name?: string;
8
+ }): Promise<{
9
+ achievements: AchievementListItem[];
10
+ total: {
11
+ all?: number;
12
+ filtered?: number;
13
+ };
14
+ }>;
15
+ interface AchievementCreateInput {
16
+ storeCode?: string;
17
+ translations: Record<string, {
18
+ name: string;
19
+ description?: string;
20
+ }>;
21
+ active?: boolean;
22
+ activity?: {
23
+ startsAt?: string;
24
+ endsAt?: string;
25
+ operator?: string;
26
+ };
27
+ limit?: {
28
+ value?: number;
29
+ interval?: {
30
+ type: string;
31
+ value?: number;
32
+ };
33
+ };
34
+ rules: Array<Record<string, unknown>>;
35
+ badgeTypeId?: string;
36
+ }
37
+ export declare function achievementCreate(input: AchievementCreateInput): Promise<{
38
+ achievementId: string;
39
+ }>;
40
+ export declare function achievementGet(input: {
41
+ storeCode?: string;
42
+ achievementId: string;
43
+ }): Promise<Record<string, unknown>>;
44
+ interface AchievementUpdateInput {
45
+ storeCode?: string;
46
+ achievementId: string;
47
+ translations: Record<string, {
48
+ name: string;
49
+ description?: string;
50
+ }>;
51
+ active?: boolean;
52
+ activity?: {
53
+ startsAt?: string;
54
+ endsAt?: string;
55
+ operator?: string;
56
+ };
57
+ limit?: {
58
+ value?: number;
59
+ interval?: {
60
+ type: string;
61
+ value?: number;
62
+ };
63
+ };
64
+ rules: Array<Record<string, unknown>>;
65
+ badgeTypeId?: string;
66
+ }
67
+ export declare function achievementUpdate(input: AchievementUpdateInput): Promise<void>;
68
+ interface AchievementPatchInput {
69
+ storeCode?: string;
70
+ achievementId: string;
71
+ active?: boolean;
72
+ translations?: Record<string, {
73
+ name?: string;
74
+ description?: string;
75
+ }>;
76
+ }
77
+ export declare function achievementPatch(input: AchievementPatchInput): Promise<void>;
78
+ export interface AchievementMemberProgress {
79
+ achievementId: string;
80
+ achievementName?: string;
81
+ achievementDescription?: string;
82
+ limitReached?: boolean;
83
+ memberProgress: {
84
+ completedCount: number;
85
+ rules: Array<{
86
+ achievementRuleId: string;
87
+ periodGoal: number;
88
+ currentPeriodValue: number;
89
+ consecutivePeriods?: number;
90
+ completedConsecutivePeriods?: number;
91
+ periodType?: string;
92
+ type?: string;
93
+ trigger?: string;
94
+ periodValue?: number;
95
+ }>;
96
+ };
97
+ }
98
+ export declare function achievementGetMemberProgress(input: {
99
+ storeCode?: string;
100
+ memberId: string;
101
+ achievementId: string;
102
+ }): Promise<AchievementMemberProgress>;
103
+ export declare function achievementListMemberAchievements(input: {
104
+ storeCode?: string;
105
+ memberId: string;
106
+ page?: number;
107
+ perPage?: number;
108
+ name?: string;
109
+ achievementId?: string;
110
+ }): Promise<{
111
+ achievements: AchievementMemberProgress[];
112
+ total: {
113
+ all?: number;
114
+ filtered?: number;
115
+ };
116
+ }>;
117
+ export {};
@@ -0,0 +1,161 @@
1
+ import { apiGet, apiPost, apiPut, apiPatch } from "../../client/http.js";
2
+ import { formatApiError } from "../../utils/errors.js";
3
+ import { getStoreCode } from "../../config.js";
4
+ export async function achievementList(input) {
5
+ const storeCode = getStoreCode(input.storeCode);
6
+ const params = new URLSearchParams();
7
+ if (input.page)
8
+ params.append("_page", String(input.page));
9
+ if (input.perPage)
10
+ params.append("_itemsOnPage", String(input.perPage));
11
+ if (input.active !== undefined)
12
+ params.append("active", String(input.active));
13
+ if (input.name)
14
+ params.append("name", input.name);
15
+ const queryString = params.toString();
16
+ const url = `/${storeCode}/achievement${queryString ? `?${queryString}` : ""}`;
17
+ try {
18
+ const response = await apiGet(url);
19
+ const achievements = (response.items || []).map((item) => ({
20
+ achievementId: item.achievementId,
21
+ name: item.name,
22
+ active: item.active,
23
+ createdAt: item.createdAt,
24
+ badgeTypeId: item.badgeTypeId,
25
+ }));
26
+ const total = response.total || {};
27
+ return {
28
+ achievements,
29
+ total: {
30
+ all: typeof total.all === "number" ? total.all : undefined,
31
+ filtered: typeof total.filtered === "number" ? total.filtered : undefined,
32
+ },
33
+ };
34
+ }
35
+ catch (error) {
36
+ throw formatApiError(error, "ol_achievement_list");
37
+ }
38
+ }
39
+ export async function achievementCreate(input) {
40
+ const storeCode = getStoreCode(input.storeCode);
41
+ const achievementPayload = {
42
+ translations: input.translations,
43
+ rules: input.rules,
44
+ };
45
+ if (input.active !== undefined)
46
+ achievementPayload.active = input.active;
47
+ if (input.activity)
48
+ achievementPayload.activity = input.activity;
49
+ if (input.limit)
50
+ achievementPayload.limit = input.limit;
51
+ if (input.badgeTypeId)
52
+ achievementPayload.badgeTypeId = input.badgeTypeId;
53
+ try {
54
+ // CRITICAL: Wrap body as { achievement: {...} }
55
+ const response = await apiPost(`/${storeCode}/achievement`, { achievement: achievementPayload });
56
+ return { achievementId: response.achievementId };
57
+ }
58
+ catch (error) {
59
+ throw formatApiError(error, "ol_achievement_create");
60
+ }
61
+ }
62
+ export async function achievementGet(input) {
63
+ const storeCode = getStoreCode(input.storeCode);
64
+ try {
65
+ const response = await apiGet(`/${storeCode}/achievement/${input.achievementId}`);
66
+ return response;
67
+ }
68
+ catch (error) {
69
+ throw formatApiError(error, "ol_achievement_get");
70
+ }
71
+ }
72
+ export async function achievementUpdate(input) {
73
+ const storeCode = getStoreCode(input.storeCode);
74
+ const achievementPayload = {
75
+ translations: input.translations,
76
+ rules: input.rules,
77
+ };
78
+ if (input.active !== undefined)
79
+ achievementPayload.active = input.active;
80
+ if (input.activity)
81
+ achievementPayload.activity = input.activity;
82
+ if (input.limit)
83
+ achievementPayload.limit = input.limit;
84
+ if (input.badgeTypeId !== undefined)
85
+ achievementPayload.badgeTypeId = input.badgeTypeId;
86
+ try {
87
+ // CRITICAL: Wrap body as { achievement: {...} }
88
+ await apiPut(`/${storeCode}/achievement/${input.achievementId}`, { achievement: achievementPayload });
89
+ }
90
+ catch (error) {
91
+ throw formatApiError(error, "ol_achievement_update");
92
+ }
93
+ }
94
+ export async function achievementPatch(input) {
95
+ const storeCode = getStoreCode(input.storeCode);
96
+ const achievementPayload = {};
97
+ if (input.active !== undefined)
98
+ achievementPayload.active = input.active;
99
+ if (input.translations)
100
+ achievementPayload.translations = input.translations;
101
+ try {
102
+ // CRITICAL: Wrap body as { achievement: {...} }
103
+ await apiPatch(`/${storeCode}/achievement/${input.achievementId}`, { achievement: achievementPayload });
104
+ }
105
+ catch (error) {
106
+ throw formatApiError(error, "ol_achievement_patch");
107
+ }
108
+ }
109
+ export async function achievementGetMemberProgress(input) {
110
+ const storeCode = getStoreCode(input.storeCode);
111
+ try {
112
+ // Try the member achievements list endpoint with achievementId filter
113
+ // The direct /achievement/{id} endpoint may not exist
114
+ const response = await apiGet(`/${storeCode}/member/${input.memberId}/achievement?achievementId=${input.achievementId}`);
115
+ // Return first matching achievement
116
+ const achievements = response.items || [];
117
+ if (achievements.length === 0) {
118
+ // If no achievements found, return empty progress
119
+ return {
120
+ achievementId: input.achievementId,
121
+ memberProgress: {
122
+ completedCount: 0,
123
+ rules: [],
124
+ },
125
+ };
126
+ }
127
+ return achievements[0];
128
+ }
129
+ catch (error) {
130
+ throw formatApiError(error, "ol_achievement_get_member_progress");
131
+ }
132
+ }
133
+ export async function achievementListMemberAchievements(input) {
134
+ const storeCode = getStoreCode(input.storeCode);
135
+ const params = new URLSearchParams();
136
+ if (input.page)
137
+ params.append("_page", String(input.page));
138
+ if (input.perPage)
139
+ params.append("_itemsOnPage", String(input.perPage));
140
+ if (input.name)
141
+ params.append("name", input.name);
142
+ if (input.achievementId)
143
+ params.append("achievementId", input.achievementId);
144
+ const queryString = params.toString();
145
+ const url = `/${storeCode}/member/${input.memberId}/achievement${queryString ? `?${queryString}` : ""}`;
146
+ try {
147
+ const response = await apiGet(url);
148
+ const achievements = response.items || [];
149
+ const total = response.total || {};
150
+ return {
151
+ achievements,
152
+ total: {
153
+ all: typeof total.all === "number" ? total.all : undefined,
154
+ filtered: typeof total.filtered === "number" ? total.filtered : undefined,
155
+ },
156
+ };
157
+ }
158
+ catch (error) {
159
+ throw formatApiError(error, "ol_achievement_list_member_achievements");
160
+ }
161
+ }