@brainerce/mcp-server 3.12.1 → 3.12.2

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/bin/http.js CHANGED
@@ -1782,7 +1782,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1782
1782
 
1783
1783
  ### Restaurant features (advanced)
1784
1784
 
1785
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1785
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1786
1786
  }
1787
1787
  function getProductReviewsSection() {
1788
1788
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3310,6 +3310,47 @@ so customers can track their shipment automatically.
3310
3310
 
3311
3311
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3312
3312
  }
3313
+ function getLoyaltySection() {
3314
+ return `# Loyalty & Rewards
3315
+
3316
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3317
+
3318
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3319
+
3320
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3321
+
3322
+ ## Status
3323
+
3324
+ \`\`\`typescript
3325
+ // requires client.setCustomerToken(auth.token)
3326
+ const status = await client.getLoyaltyStatus();
3327
+ // { enrolled, pointsBalance, lifetimeEarned, program: { pointsName, currencyRatio, status } | null }
3328
+ \`\`\`
3329
+
3330
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3331
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3332
+
3333
+ ## Enroll (only if auto-enroll is off)
3334
+
3335
+ \`\`\`typescript
3336
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3337
+ \`\`\`
3338
+
3339
+ ## Rewards & redemption
3340
+
3341
+ \`\`\`typescript
3342
+ const rewards = await client.getAvailableRewards();
3343
+ // [{ id, name, description, pointsCost, discountValue, minOrderAmount, maxUsesPerUser, isActive }]
3344
+
3345
+ const { couponCode, discountValue, pointsBalance } = await client.redeemLoyaltyReward(rewardId);
3346
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3347
+ \`\`\`
3348
+
3349
+ - \`redeemLoyaltyReward\` mints a one-time fixed-amount coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3350
+ - Throws if the customer has insufficient points. If minting fails, points are refunded automatically.
3351
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3352
+ `;
3353
+ }
3313
3354
  function getSectionByTopic(topic, connectionId, currency) {
3314
3355
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3315
3356
  const cur = currency || "USD";
@@ -3336,6 +3377,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3336
3377
  return getInventorySection();
3337
3378
  case "discounts":
3338
3379
  return getDiscountsSection();
3380
+ case "loyalty":
3381
+ return getLoyaltySection();
3339
3382
  case "recommendations":
3340
3383
  return getRecommendationsSection();
3341
3384
  case "reviews":
@@ -3502,6 +3545,7 @@ var GET_SDK_DOCS_SCHEMA = {
3502
3545
  "order-confirmation",
3503
3546
  "inventory",
3504
3547
  "discounts",
3548
+ "loyalty",
3505
3549
  "recommendations",
3506
3550
  "reviews",
3507
3551
  "product-customization-fields",
@@ -4839,6 +4883,43 @@ interface TaxEstimateResponse {
4839
4883
  // Non-binding by design: the authoritative tax runs at checkout against the
4840
4884
  // full shipping address. The country comes from your edge runtime
4841
4885
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4886
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4887
+ interface LoyaltyStatus {
4888
+ enrolled: boolean;
4889
+ pointsBalance: number; // redeemable points (pending earns excluded)
4890
+ lifetimeEarned: number;
4891
+ program: {
4892
+ pointsName: string; // e.g. "points", "coins"
4893
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4894
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4895
+ } | null; // null when the store has no loyalty program
4896
+ }
4897
+
4898
+ interface LoyaltyReward {
4899
+ id: string;
4900
+ name: string;
4901
+ description: string | null;
4902
+ pointsCost: number;
4903
+ discountValue: number; // fixed amount off, store currency
4904
+ minOrderAmount: number | null;
4905
+ maxUsesPerUser: number;
4906
+ isActive: boolean;
4907
+ createdAt: string;
4908
+ updatedAt: string;
4909
+ }
4910
+
4911
+ interface RedeemRewardResult {
4912
+ couponCode: string; // one-time coupon to apply at checkout
4913
+ discountValue: number;
4914
+ pointsSpent: number;
4915
+ pointsBalance: number; // remaining balance after redemption
4916
+ }
4917
+
4918
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4919
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4920
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4921
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4922
+ `;
4842
4923
  var TYPES_BY_DOMAIN = {
4843
4924
  products: PRODUCTS_TYPES,
4844
4925
  cart: CART_TYPES,
@@ -4851,7 +4932,8 @@ var TYPES_BY_DOMAIN = {
4851
4932
  reviews: REVIEWS_TYPES,
4852
4933
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4853
4934
  content: CONTENT_TYPES,
4854
- regions: REGIONS_TYPES
4935
+ regions: REGIONS_TYPES,
4936
+ loyalty: LOYALTY_TYPES
4855
4937
  };
4856
4938
  function getTypesByDomain(domain) {
4857
4939
  if (domain === "all") {
@@ -4885,6 +4967,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4885
4967
  "modifier-groups",
4886
4968
  "content",
4887
4969
  "regions",
4970
+ "loyalty",
4888
4971
  "all"
4889
4972
  ]).describe(
4890
4973
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6331,6 +6414,9 @@ function formatCapabilities(caps) {
6331
6414
  lines.push(
6332
6415
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6333
6416
  );
6417
+ lines.push(
6418
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6419
+ );
6334
6420
  lines.push("");
6335
6421
  lines.push("## Connection Settings");
6336
6422
  lines.push(
@@ -6376,6 +6462,11 @@ function formatCapabilities(caps) {
6376
6462
  if (caps.features.hasCoupons) {
6377
6463
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6378
6464
  }
6465
+ if (caps.features.hasLoyaltyProgram) {
6466
+ suggestions.push(
6467
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus(), getAvailableRewards(), and redeemLoyaltyReward(id). Note: loyalty is NOT available in vibe-coded mode."
6468
+ );
6469
+ }
6379
6470
  if (caps.features.hasCheckoutCustomFields) {
6380
6471
  suggestions.push(
6381
6472
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/dist/bin/stdio.js CHANGED
@@ -1779,7 +1779,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1779
1779
 
1780
1780
  ### Restaurant features (advanced)
1781
1781
 
1782
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1782
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1783
1783
  }
1784
1784
  function getProductReviewsSection() {
1785
1785
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3307,6 +3307,47 @@ so customers can track their shipment automatically.
3307
3307
 
3308
3308
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3309
3309
  }
3310
+ function getLoyaltySection() {
3311
+ return `# Loyalty & Rewards
3312
+
3313
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3314
+
3315
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3316
+
3317
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3318
+
3319
+ ## Status
3320
+
3321
+ \`\`\`typescript
3322
+ // requires client.setCustomerToken(auth.token)
3323
+ const status = await client.getLoyaltyStatus();
3324
+ // { enrolled, pointsBalance, lifetimeEarned, program: { pointsName, currencyRatio, status } | null }
3325
+ \`\`\`
3326
+
3327
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3328
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3329
+
3330
+ ## Enroll (only if auto-enroll is off)
3331
+
3332
+ \`\`\`typescript
3333
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3334
+ \`\`\`
3335
+
3336
+ ## Rewards & redemption
3337
+
3338
+ \`\`\`typescript
3339
+ const rewards = await client.getAvailableRewards();
3340
+ // [{ id, name, description, pointsCost, discountValue, minOrderAmount, maxUsesPerUser, isActive }]
3341
+
3342
+ const { couponCode, discountValue, pointsBalance } = await client.redeemLoyaltyReward(rewardId);
3343
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3344
+ \`\`\`
3345
+
3346
+ - \`redeemLoyaltyReward\` mints a one-time fixed-amount coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3347
+ - Throws if the customer has insufficient points. If minting fails, points are refunded automatically.
3348
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3349
+ `;
3350
+ }
3310
3351
  function getSectionByTopic(topic, connectionId, currency) {
3311
3352
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3312
3353
  const cur = currency || "USD";
@@ -3333,6 +3374,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3333
3374
  return getInventorySection();
3334
3375
  case "discounts":
3335
3376
  return getDiscountsSection();
3377
+ case "loyalty":
3378
+ return getLoyaltySection();
3336
3379
  case "recommendations":
3337
3380
  return getRecommendationsSection();
3338
3381
  case "reviews":
@@ -3499,6 +3542,7 @@ var GET_SDK_DOCS_SCHEMA = {
3499
3542
  "order-confirmation",
3500
3543
  "inventory",
3501
3544
  "discounts",
3545
+ "loyalty",
3502
3546
  "recommendations",
3503
3547
  "reviews",
3504
3548
  "product-customization-fields",
@@ -4836,6 +4880,43 @@ interface TaxEstimateResponse {
4836
4880
  // Non-binding by design: the authoritative tax runs at checkout against the
4837
4881
  // full shipping address. The country comes from your edge runtime
4838
4882
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4883
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4884
+ interface LoyaltyStatus {
4885
+ enrolled: boolean;
4886
+ pointsBalance: number; // redeemable points (pending earns excluded)
4887
+ lifetimeEarned: number;
4888
+ program: {
4889
+ pointsName: string; // e.g. "points", "coins"
4890
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4891
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4892
+ } | null; // null when the store has no loyalty program
4893
+ }
4894
+
4895
+ interface LoyaltyReward {
4896
+ id: string;
4897
+ name: string;
4898
+ description: string | null;
4899
+ pointsCost: number;
4900
+ discountValue: number; // fixed amount off, store currency
4901
+ minOrderAmount: number | null;
4902
+ maxUsesPerUser: number;
4903
+ isActive: boolean;
4904
+ createdAt: string;
4905
+ updatedAt: string;
4906
+ }
4907
+
4908
+ interface RedeemRewardResult {
4909
+ couponCode: string; // one-time coupon to apply at checkout
4910
+ discountValue: number;
4911
+ pointsSpent: number;
4912
+ pointsBalance: number; // remaining balance after redemption
4913
+ }
4914
+
4915
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4916
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4917
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4918
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4919
+ `;
4839
4920
  var TYPES_BY_DOMAIN = {
4840
4921
  products: PRODUCTS_TYPES,
4841
4922
  cart: CART_TYPES,
@@ -4848,7 +4929,8 @@ var TYPES_BY_DOMAIN = {
4848
4929
  reviews: REVIEWS_TYPES,
4849
4930
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4850
4931
  content: CONTENT_TYPES,
4851
- regions: REGIONS_TYPES
4932
+ regions: REGIONS_TYPES,
4933
+ loyalty: LOYALTY_TYPES
4852
4934
  };
4853
4935
  function getTypesByDomain(domain) {
4854
4936
  if (domain === "all") {
@@ -4882,6 +4964,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4882
4964
  "modifier-groups",
4883
4965
  "content",
4884
4966
  "regions",
4967
+ "loyalty",
4885
4968
  "all"
4886
4969
  ]).describe(
4887
4970
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6328,6 +6411,9 @@ function formatCapabilities(caps) {
6328
6411
  lines.push(
6329
6412
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6330
6413
  );
6414
+ lines.push(
6415
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6416
+ );
6331
6417
  lines.push("");
6332
6418
  lines.push("## Connection Settings");
6333
6419
  lines.push(
@@ -6373,6 +6459,11 @@ function formatCapabilities(caps) {
6373
6459
  if (caps.features.hasCoupons) {
6374
6460
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6375
6461
  }
6462
+ if (caps.features.hasLoyaltyProgram) {
6463
+ suggestions.push(
6464
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus(), getAvailableRewards(), and redeemLoyaltyReward(id). Note: loyalty is NOT available in vibe-coded mode."
6465
+ );
6466
+ }
6376
6467
  if (caps.features.hasCheckoutCustomFields) {
6377
6468
  suggestions.push(
6378
6469
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/dist/index.d.mts CHANGED
@@ -75,6 +75,8 @@ interface StoreCapabilities {
75
75
  hasCheckoutCustomFields: boolean;
76
76
  /** True when at least one PUBLISHED Content row exists for the store. */
77
77
  hasContent?: boolean;
78
+ /** True when the store has an ACTIVE loyalty program. Storefront-mode only. */
79
+ hasLoyaltyProgram?: boolean;
78
80
  };
79
81
  }
80
82
  declare function fetchStoreCapabilities(connectionId: string, baseUrl?: string): Promise<StoreCapabilities>;
package/dist/index.d.ts CHANGED
@@ -75,6 +75,8 @@ interface StoreCapabilities {
75
75
  hasCheckoutCustomFields: boolean;
76
76
  /** True when at least one PUBLISHED Content row exists for the store. */
77
77
  hasContent?: boolean;
78
+ /** True when the store has an ACTIVE loyalty program. Storefront-mode only. */
79
+ hasLoyaltyProgram?: boolean;
78
80
  };
79
81
  }
80
82
  declare function fetchStoreCapabilities(connectionId: string, baseUrl?: string): Promise<StoreCapabilities>;
package/dist/index.js CHANGED
@@ -1805,7 +1805,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1805
1805
 
1806
1806
  ### Restaurant features (advanced)
1807
1807
 
1808
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1808
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1809
1809
  }
1810
1810
  function getProductReviewsSection() {
1811
1811
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3333,6 +3333,47 @@ so customers can track their shipment automatically.
3333
3333
 
3334
3334
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3335
3335
  }
3336
+ function getLoyaltySection() {
3337
+ return `# Loyalty & Rewards
3338
+
3339
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3340
+
3341
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3342
+
3343
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3344
+
3345
+ ## Status
3346
+
3347
+ \`\`\`typescript
3348
+ // requires client.setCustomerToken(auth.token)
3349
+ const status = await client.getLoyaltyStatus();
3350
+ // { enrolled, pointsBalance, lifetimeEarned, program: { pointsName, currencyRatio, status } | null }
3351
+ \`\`\`
3352
+
3353
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3354
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3355
+
3356
+ ## Enroll (only if auto-enroll is off)
3357
+
3358
+ \`\`\`typescript
3359
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3360
+ \`\`\`
3361
+
3362
+ ## Rewards & redemption
3363
+
3364
+ \`\`\`typescript
3365
+ const rewards = await client.getAvailableRewards();
3366
+ // [{ id, name, description, pointsCost, discountValue, minOrderAmount, maxUsesPerUser, isActive }]
3367
+
3368
+ const { couponCode, discountValue, pointsBalance } = await client.redeemLoyaltyReward(rewardId);
3369
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3370
+ \`\`\`
3371
+
3372
+ - \`redeemLoyaltyReward\` mints a one-time fixed-amount coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3373
+ - Throws if the customer has insufficient points. If minting fails, points are refunded automatically.
3374
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3375
+ `;
3376
+ }
3336
3377
  function getSectionByTopic(topic, connectionId, currency) {
3337
3378
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3338
3379
  const cur = currency || "USD";
@@ -3359,6 +3400,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3359
3400
  return getInventorySection();
3360
3401
  case "discounts":
3361
3402
  return getDiscountsSection();
3403
+ case "loyalty":
3404
+ return getLoyaltySection();
3362
3405
  case "recommendations":
3363
3406
  return getRecommendationsSection();
3364
3407
  case "reviews":
@@ -3525,6 +3568,7 @@ var GET_SDK_DOCS_SCHEMA = {
3525
3568
  "order-confirmation",
3526
3569
  "inventory",
3527
3570
  "discounts",
3571
+ "loyalty",
3528
3572
  "recommendations",
3529
3573
  "reviews",
3530
3574
  "product-customization-fields",
@@ -4862,6 +4906,43 @@ interface TaxEstimateResponse {
4862
4906
  // Non-binding by design: the authoritative tax runs at checkout against the
4863
4907
  // full shipping address. The country comes from your edge runtime
4864
4908
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4909
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4910
+ interface LoyaltyStatus {
4911
+ enrolled: boolean;
4912
+ pointsBalance: number; // redeemable points (pending earns excluded)
4913
+ lifetimeEarned: number;
4914
+ program: {
4915
+ pointsName: string; // e.g. "points", "coins"
4916
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4917
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4918
+ } | null; // null when the store has no loyalty program
4919
+ }
4920
+
4921
+ interface LoyaltyReward {
4922
+ id: string;
4923
+ name: string;
4924
+ description: string | null;
4925
+ pointsCost: number;
4926
+ discountValue: number; // fixed amount off, store currency
4927
+ minOrderAmount: number | null;
4928
+ maxUsesPerUser: number;
4929
+ isActive: boolean;
4930
+ createdAt: string;
4931
+ updatedAt: string;
4932
+ }
4933
+
4934
+ interface RedeemRewardResult {
4935
+ couponCode: string; // one-time coupon to apply at checkout
4936
+ discountValue: number;
4937
+ pointsSpent: number;
4938
+ pointsBalance: number; // remaining balance after redemption
4939
+ }
4940
+
4941
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4942
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4943
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4944
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4945
+ `;
4865
4946
  var TYPES_BY_DOMAIN = {
4866
4947
  products: PRODUCTS_TYPES,
4867
4948
  cart: CART_TYPES,
@@ -4874,7 +4955,8 @@ var TYPES_BY_DOMAIN = {
4874
4955
  reviews: REVIEWS_TYPES,
4875
4956
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4876
4957
  content: CONTENT_TYPES,
4877
- regions: REGIONS_TYPES
4958
+ regions: REGIONS_TYPES,
4959
+ loyalty: LOYALTY_TYPES
4878
4960
  };
4879
4961
  function getTypesByDomain(domain) {
4880
4962
  if (domain === "all") {
@@ -4908,6 +4990,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4908
4990
  "modifier-groups",
4909
4991
  "content",
4910
4992
  "regions",
4993
+ "loyalty",
4911
4994
  "all"
4912
4995
  ]).describe(
4913
4996
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6354,6 +6437,9 @@ function formatCapabilities(caps) {
6354
6437
  lines.push(
6355
6438
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6356
6439
  );
6440
+ lines.push(
6441
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6442
+ );
6357
6443
  lines.push("");
6358
6444
  lines.push("## Connection Settings");
6359
6445
  lines.push(
@@ -6399,6 +6485,11 @@ function formatCapabilities(caps) {
6399
6485
  if (caps.features.hasCoupons) {
6400
6486
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6401
6487
  }
6488
+ if (caps.features.hasLoyaltyProgram) {
6489
+ suggestions.push(
6490
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus(), getAvailableRewards(), and redeemLoyaltyReward(id). Note: loyalty is NOT available in vibe-coded mode."
6491
+ );
6492
+ }
6402
6493
  if (caps.features.hasCheckoutCustomFields) {
6403
6494
  suggestions.push(
6404
6495
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/dist/index.mjs CHANGED
@@ -1773,7 +1773,7 @@ The merchant can hide a group entirely for one variant by setting \`maxOverride:
1773
1773
 
1774
1774
  ### Restaurant features (advanced)
1775
1775
 
1776
- Allergens (informational chips), scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1776
+ Scheduled availability windows, nested combos (depth \u2264 3 via \`nestedByModifierId\`), and downsell modifiers (negative \`priceDelta\`) are all built on the same data model. See INTEGRATION-OPTIONAL.md "Restaurant / build-your-own products" for those flows.`;
1777
1777
  }
1778
1778
  function getProductReviewsSection() {
1779
1779
  return `## Product Reviews (\u2605 ratings + JSON-LD for SEO)
@@ -3301,6 +3301,47 @@ so customers can track their shipment automatically.
3301
3301
 
3302
3302
  **SDK mode:** \`createShippingLabel()\` requires \`apiKey\` (admin mode only).`;
3303
3303
  }
3304
+ function getLoyaltySection() {
3305
+ return `# Loyalty & Rewards
3306
+
3307
+ Logged-in customers earn points on paid orders and redeem them for a one-time discount coupon at checkout.
3308
+
3309
+ > **Storefront mode only.** These methods work only with \`new BrainerceClient({ storeId })\` + a logged-in \`customerToken\`. There is **no vibe-coded route** \u2014 in \`salesChannelId: 'vc_*'\` mode they throw. Skip loyalty if you connect vibe-coded.
3310
+
3311
+ Points are earned automatically by the platform when an order is paid \u2014 there is no earn call from the storefront. Your job is to **show** balance/rewards and to **redeem**.
3312
+
3313
+ ## Status
3314
+
3315
+ \`\`\`typescript
3316
+ // requires client.setCustomerToken(auth.token)
3317
+ const status = await client.getLoyaltyStatus();
3318
+ // { enrolled, pointsBalance, lifetimeEarned, program: { pointsName, currencyRatio, status } | null }
3319
+ \`\`\`
3320
+
3321
+ - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3322
+ - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3323
+
3324
+ ## Enroll (only if auto-enroll is off)
3325
+
3326
+ \`\`\`typescript
3327
+ await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3328
+ \`\`\`
3329
+
3330
+ ## Rewards & redemption
3331
+
3332
+ \`\`\`typescript
3333
+ const rewards = await client.getAvailableRewards();
3334
+ // [{ id, name, description, pointsCost, discountValue, minOrderAmount, maxUsesPerUser, isActive }]
3335
+
3336
+ const { couponCode, discountValue, pointsBalance } = await client.redeemLoyaltyReward(rewardId);
3337
+ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3338
+ \`\`\`
3339
+
3340
+ - \`redeemLoyaltyReward\` mints a one-time fixed-amount coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3341
+ - Throws if the customer has insufficient points. If minting fails, points are refunded automatically.
3342
+ - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3343
+ `;
3344
+ }
3304
3345
  function getSectionByTopic(topic, connectionId, currency) {
3305
3346
  const cid = connectionId || "vc_YOUR_CONNECTION_ID";
3306
3347
  const cur = currency || "USD";
@@ -3327,6 +3368,8 @@ function getSectionByTopic(topic, connectionId, currency) {
3327
3368
  return getInventorySection();
3328
3369
  case "discounts":
3329
3370
  return getDiscountsSection();
3371
+ case "loyalty":
3372
+ return getLoyaltySection();
3330
3373
  case "recommendations":
3331
3374
  return getRecommendationsSection();
3332
3375
  case "reviews":
@@ -3493,6 +3536,7 @@ var GET_SDK_DOCS_SCHEMA = {
3493
3536
  "order-confirmation",
3494
3537
  "inventory",
3495
3538
  "discounts",
3539
+ "loyalty",
3496
3540
  "recommendations",
3497
3541
  "reviews",
3498
3542
  "product-customization-fields",
@@ -4830,6 +4874,43 @@ interface TaxEstimateResponse {
4830
4874
  // Non-binding by design: the authoritative tax runs at checkout against the
4831
4875
  // full shipping address. The country comes from your edge runtime
4832
4876
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4877
+ var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4878
+ interface LoyaltyStatus {
4879
+ enrolled: boolean;
4880
+ pointsBalance: number; // redeemable points (pending earns excluded)
4881
+ lifetimeEarned: number;
4882
+ program: {
4883
+ pointsName: string; // e.g. "points", "coins"
4884
+ currencyRatio: number; // points needed to redeem 1 unit of store currency
4885
+ status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4886
+ } | null; // null when the store has no loyalty program
4887
+ }
4888
+
4889
+ interface LoyaltyReward {
4890
+ id: string;
4891
+ name: string;
4892
+ description: string | null;
4893
+ pointsCost: number;
4894
+ discountValue: number; // fixed amount off, store currency
4895
+ minOrderAmount: number | null;
4896
+ maxUsesPerUser: number;
4897
+ isActive: boolean;
4898
+ createdAt: string;
4899
+ updatedAt: string;
4900
+ }
4901
+
4902
+ interface RedeemRewardResult {
4903
+ couponCode: string; // one-time coupon to apply at checkout
4904
+ discountValue: number;
4905
+ pointsSpent: number;
4906
+ pointsBalance: number; // remaining balance after redemption
4907
+ }
4908
+
4909
+ // client.getLoyaltyStatus(): Promise<LoyaltyStatus>
4910
+ // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4911
+ // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4912
+ // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
4913
+ `;
4833
4914
  var TYPES_BY_DOMAIN = {
4834
4915
  products: PRODUCTS_TYPES,
4835
4916
  cart: CART_TYPES,
@@ -4842,7 +4923,8 @@ var TYPES_BY_DOMAIN = {
4842
4923
  reviews: REVIEWS_TYPES,
4843
4924
  "modifier-groups": MODIFIER_GROUPS_TYPES,
4844
4925
  content: CONTENT_TYPES,
4845
- regions: REGIONS_TYPES
4926
+ regions: REGIONS_TYPES,
4927
+ loyalty: LOYALTY_TYPES
4846
4928
  };
4847
4929
  function getTypesByDomain(domain) {
4848
4930
  if (domain === "all") {
@@ -4876,6 +4958,7 @@ var GET_TYPE_DEFINITIONS_SCHEMA = {
4876
4958
  "modifier-groups",
4877
4959
  "content",
4878
4960
  "regions",
4961
+ "loyalty",
4879
4962
  "all"
4880
4963
  ]).describe(
4881
4964
  'The domain of types to retrieve. Use "helpers" for helper function signatures and common types like StoreInfo, PaginatedResponse. Use "content" for FAQ / Footer / Header / Announcement / RichText / Page. Use "regions" for Region / TaxClass admin types.'
@@ -6322,6 +6405,9 @@ function formatCapabilities(caps) {
6322
6405
  lines.push(
6323
6406
  caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
6324
6407
  );
6408
+ lines.push(
6409
+ caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6410
+ );
6325
6411
  lines.push("");
6326
6412
  lines.push("## Connection Settings");
6327
6413
  lines.push(
@@ -6367,6 +6453,11 @@ function formatCapabilities(caps) {
6367
6453
  if (caps.features.hasCoupons) {
6368
6454
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
6369
6455
  }
6456
+ if (caps.features.hasLoyaltyProgram) {
6457
+ suggestions.push(
6458
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus(), getAvailableRewards(), and redeemLoyaltyReward(id). Note: loyalty is NOT available in vibe-coded mode."
6459
+ );
6460
+ }
6370
6461
  if (caps.features.hasCheckoutCustomFields) {
6371
6462
  suggestions.push(
6372
6463
  `Checkout custom fields are configured. Build a step between shipping/pickup and payment using getCheckoutCustomFields() + setCheckoutCustomFields(). Render checkout.appliedSurcharges in the order summary so customers see what they're paying for. Use get-code-example("checkout-custom-fields").`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainerce/mcp-server",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
4
4
  "packageManager": "pnpm@9.0.0",
5
5
  "description": "Framework-agnostic domain knowledge API for Brainerce. Provides SDK docs, types, business flows, critical rules, and store capabilities to AI tools like Lovable, Cursor, Bolt, v0, and Claude Code. Does NOT provide framework-specific boilerplate — clients build in whatever framework fits.",
6
6
  "bin": {