@brainerce/mcp-server 3.5.0 → 3.5.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.
package/dist/index.mjs CHANGED
@@ -87,7 +87,7 @@ npm install brainerce
87
87
  import { BrainerceClient } from 'brainerce';
88
88
 
89
89
  export const client = new BrainerceClient({
90
- connectionId: '${connectionId}',
90
+ salesChannelId: '${connectionId}',
91
91
  });
92
92
 
93
93
  // Cart helpers \u2014 save cart ID to localStorage
@@ -189,7 +189,7 @@ async function startCheckout() {
189
189
  ### Full Checkout Flow (Multi-Provider)
190
190
 
191
191
  1. Customer fills cart
192
- 2. Customer optionally applies coupon (cart page)
192
+ 2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
193
193
  3. Detect payment providers \u2192 \`getPaymentProviders()\`
194
194
  4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
195
195
  5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
@@ -207,7 +207,7 @@ import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-
207
207
 
208
208
  function CheckoutPage() {
209
209
  const [paymentData, setPaymentData] = useState<{
210
- clientSecret: string; provider: string; checkoutId: string;
210
+ clientSecret: string; provider: string; checkoutId: string; clientSdk?: { renderType: string };
211
211
  } | null>(null);
212
212
  const [stripePromise, setStripePromise] = useState<ReturnType<typeof loadStripe> | null>(null);
213
213
  const [paypalClientId, setPaypalClientId] = useState<string | null>(null);
@@ -269,7 +269,7 @@ function CheckoutPage() {
269
269
  // saveCard: customerOptedIn, // optional opt-in
270
270
  });
271
271
 
272
- setPaymentData({ clientSecret: paymentIntent.clientSecret, provider: paymentIntent.provider, checkoutId });
272
+ setPaymentData({ clientSecret: paymentIntent.clientSecret, provider: paymentIntent.provider, checkoutId, clientSdk: paymentIntent.clientSdk });
273
273
 
274
274
  // Step 5: Initialize the correct payment provider
275
275
  if (paymentIntent.provider === 'stripe' && stripeProvider) {
@@ -879,6 +879,10 @@ function ProductPage({ product, storeInfo }: { product: Product; storeInfo: Stor
879
879
  ? getVariantPrice(selectedVariant, product.basePrice).toString()
880
880
  : product.salePrice || product.basePrice;
881
881
 
882
+ // \u2705 For catalog cards: use pre-computed priceMin/priceMax instead of iterating variants
883
+ // product.priceMin / product.priceMax are always set for VARIABLE products with explicit variant prices.
884
+ // product.priceVaries is true when the range should be shown as "\u20AA49 \u2013 \u20AA199".
885
+
882
886
  // Build attribute buttons (size, color, etc.)
883
887
  const allOptions = product.variants?.map(v => getVariantOptions(v)) || [];
884
888
  const attrNames = [...new Set(allOptions.flatMap(opts => opts.map(o => o.name)))];
@@ -1093,6 +1097,7 @@ const total = cart.total;
1093
1097
 
1094
1098
  ### Coupons
1095
1099
 
1100
+ **On the cart page** (before checkout is created):
1096
1101
  \`\`\`typescript
1097
1102
  // Apply coupon \u2014 returns updated Cart with discountAmount
1098
1103
  const updatedCart = await client.applyCoupon(cartId, 'SAVE20');
@@ -1100,13 +1105,24 @@ console.log(updatedCart.discountAmount); // "10.00"
1100
1105
  console.log(updatedCart.couponCode); // "SAVE20"
1101
1106
 
1102
1107
  // Remove coupon
1103
- const updatedCart = await client.removeCoupon(cartId);
1108
+ await client.removeCoupon(cartId);
1104
1109
 
1105
1110
  // Calculate totals including discount
1106
1111
  const totals = getCartTotals(cart); // { subtotal, discount, shipping, total }
1107
1112
  \`\`\`
1108
1113
 
1109
- Add a coupon code input on the cart page. Coupons only work on server \`Cart\` (has \`id\` field). Checkout inherits the coupon \u2014 show \`checkout.discountAmount\` and \`checkout.couponCode\` in checkout order summary.
1114
+ **On the checkout page** (after checkout session exists \u2014 ALWAYS use this when checkoutId is available):
1115
+ \`\`\`typescript
1116
+ // Applies to cart AND updates checkout totals in one call
1117
+ const checkout = await client.applyCheckoutCoupon(checkoutId, 'SAVE20');
1118
+ console.log(checkout.discountAmount); // "10.00"
1119
+ console.log(checkout.total); // correctly updated total
1120
+
1121
+ // Remove coupon from checkout
1122
+ await client.removeCheckoutCoupon(checkoutId);
1123
+ \`\`\`
1124
+
1125
+ > \u26A0\uFE0F **Critical:** if a checkout session already exists, ALWAYS use \`applyCheckoutCoupon(checkoutId, code)\`. Using \`applyCoupon(cartId, code)\` after checkout creation does NOT update the checkout total \u2014 payment will charge the original amount. Show \`checkout.discountAmount\` and \`checkout.couponCode\` in the order summary.
1110
1126
 
1111
1127
  ### \u26A0\uFE0F Checkout Order Summary \u2014 Use checkout.lineItems, NOT cart.items!
1112
1128
 
@@ -1926,7 +1942,7 @@ subsequent SDK call sends the \`Accept-Language\` header automatically:
1926
1942
  \`\`\`typescript
1927
1943
  import { BrainerceClient } from 'brainerce';
1928
1944
 
1929
- const client = new BrainerceClient({ connectionId: 'vc_...' });
1945
+ const client = new BrainerceClient({ salesChannelId: 'vc_...' });
1930
1946
  client.setLocale('he'); // done \u2014 all calls are now in Hebrew
1931
1947
  \`\`\`
1932
1948
 
@@ -2643,6 +2659,9 @@ interface Product {
2643
2659
  basePrice: string; // Use parseFloat() for calculations
2644
2660
  salePrice?: string | null;
2645
2661
  costPrice?: string | null;
2662
+ priceMin?: string | null; // Lowest variant price (VARIABLE products). Use for catalog/JSON-LD range display.
2663
+ priceMax?: string | null; // Highest variant price (VARIABLE products).
2664
+ priceVaries?: boolean; // true when variant prices differ \u2014 show "\u20AA49 \u2013 \u20AA199" range.
2646
2665
  status: string;
2647
2666
  type: 'SIMPLE' | 'VARIABLE';
2648
2667
  isDownloadable?: boolean;
@@ -3213,9 +3232,9 @@ var HELPERS_TYPES = `// ---- Helper Functions (import from 'brainerce') ----
3213
3232
  // Price helpers
3214
3233
  function formatPrice(priceString: string | number | undefined | null, options?: { currency?: string; locale?: string; }): string;
3215
3234
  function getProductPrice(product: Pick<Product, 'basePrice' | 'salePrice'>): number;
3216
- function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice'>): {
3235
+ function getProductPriceInfo(product: Pick<Product, 'basePrice' | 'salePrice' | 'discount' | 'priceMin' | 'priceVaries'>): {
3217
3236
  price: number; originalPrice: number; isOnSale: boolean; discountAmount: number; discountPercent: number;
3218
- };
3237
+ }; // Falls back to priceMin when basePrice=0 (VARIABLE products)
3219
3238
  function getVariantPrice(variant: Pick<ProductVariant, 'price' | 'salePrice'>, productBasePrice: string): number;
3220
3239
 
3221
3240
  // Cart helpers
@@ -3717,7 +3736,7 @@ function selectVariant(product: Product, selections: Record<string, string>) {
3717
3736
  import { client } from './brainerce';
3718
3737
 
3719
3738
  // 1. Submit the address + email. Email is REQUIRED on the DTO.
3720
- const { shippingRates } = await client.setShippingAddress(checkoutId, {
3739
+ const { checkout, rates } = await client.setShippingAddress(checkoutId, {
3721
3740
  email,
3722
3741
  firstName,
3723
3742
  lastName,
@@ -3728,10 +3747,11 @@ const { shippingRates } = await client.setShippingAddress(checkoutId, {
3728
3747
  postalCode,
3729
3748
  country,
3730
3749
  });
3750
+ // rates = available shipping rates; checkout = updated checkout object
3731
3751
 
3732
3752
  // 2. Let the customer pick one of the returned rates.
3733
- const chosen = shippingRates[0]; // user selection
3734
- await client.setShippingMethod(checkoutId, chosen.id);
3753
+ const chosen = rates[0]; // user selection
3754
+ await client.selectShippingMethod(checkoutId, chosen.id);
3735
3755
 
3736
3756
  // 3. Re-fetch the checkout to get updated totals (tax may change after address).
3737
3757
  const checkout = await client.getCheckout(checkoutId);
@@ -3785,21 +3805,21 @@ if (providers.length === 0) {
3785
3805
  }
3786
3806
 
3787
3807
  const active = providers[0];`,
3788
- "checkout-stripe-confirm": `// Stripe confirm flow. Requires @stripe/stripe-js and @stripe/react-stripe-js
3789
- // (or the vanilla Stripe.js browser SDK).
3808
+ "checkout-stripe-confirm": `// Stripe confirm flow. Requires @stripe/stripe-js.
3790
3809
  import { loadStripe } from '@stripe/stripe-js';
3791
3810
  import { client } from './brainerce';
3792
3811
 
3793
3812
  const stripe = await loadStripe(stripePublicKey);
3794
3813
  if (!stripe) throw new Error('Stripe failed to load');
3795
3814
 
3796
- // The SDK returns a clientSecret via the provider's config after setPaymentMethod.
3797
- const { clientSecret } = await client.setPaymentMethod(checkoutId, {
3798
- providerId: stripeProvider.id,
3815
+ // Create a payment intent \u2014 returns clientSecret for Stripe Elements.
3816
+ const intent = await client.createPaymentIntent(checkoutId, {
3817
+ successUrl: \`\${window.location.origin}/order-confirmation?checkoutId=\${checkoutId}\`,
3818
+ cancelUrl: \`\${window.location.origin}/checkout?error=cancelled\`,
3799
3819
  });
3800
3820
 
3801
- // Use Stripe Elements to collect the card, then confirm:
3802
- const result = await stripe.confirmCardPayment(clientSecret, {
3821
+ // Use Stripe Elements to confirm:
3822
+ const result = await stripe.confirmCardPayment(intent.clientSecret, {
3803
3823
  payment_method: { card: cardElement },
3804
3824
  });
3805
3825
 
@@ -3808,21 +3828,21 @@ if (result.error) {
3808
3828
  return;
3809
3829
  }
3810
3830
 
3811
- // Success \u2014 redirect to your confirmation page with checkoutId in the URL.
3831
+ // Success \u2014 redirect to confirmation page.
3812
3832
  // The confirmation page runs handlePaymentSuccess + waitForOrder.
3813
3833
  window.location.assign(\`/order-confirmation?checkoutId=\${checkoutId}\`);`,
3814
3834
  "checkout-paypal-confirm": `// PayPal confirm flow. Use the PayPal JS SDK (render a PayPal button component).
3815
3835
  import { client } from './brainerce';
3816
3836
 
3817
- // When the PayPal button's onApprove fires with the PayPal order ID:
3818
- async function onPayPalApprove(paypalOrderId: string) {
3819
- await client.setPaymentMethod(checkoutId, {
3820
- providerId: paypalProvider.id,
3821
- providerData: { paypalOrderId },
3822
- });
3837
+ // Create a payment intent first \u2014 for PayPal this returns the PayPal order ID.
3838
+ const intent = await client.createPaymentIntent(checkoutId, {
3839
+ successUrl: \`\${window.location.origin}/order-confirmation?checkoutId=\${checkoutId}\`,
3840
+ cancelUrl: \`\${window.location.origin}/checkout?error=cancelled\`,
3841
+ });
3823
3842
 
3824
- // PayPal confirmation \u2014 the SDK captures the order server-side.
3825
- // After this resolves, redirect to confirmation.
3843
+ // Render a PayPal button using intent.clientSdk (provider SDK config).
3844
+ // When the PayPal button's onApprove fires, redirect to confirmation:
3845
+ function onPayPalApprove() {
3826
3846
  window.location.assign(\`/order-confirmation?checkoutId=\${checkoutId}\`);
3827
3847
  }`,
3828
3848
  "checkout-sandbox-confirm": `// Sandbox payment \u2014 store has sandboxPaymentsEnabled=true. No real charge.
@@ -3865,7 +3885,7 @@ async function registerAndMaybeVerify(input: {
3865
3885
  firstName: string;
3866
3886
  lastName: string;
3867
3887
  }) {
3868
- const result = await client.register(input);
3888
+ const result = await client.registerCustomer(input);
3869
3889
  if (result.requiresVerification) {
3870
3890
  // Route the user to your verify-email UI. Do NOT treat them as logged in.
3871
3891
  return { state: 'needs-verification' as const };
@@ -3886,7 +3906,7 @@ async function resendCode() {
3886
3906
  import { client } from './brainerce';
3887
3907
 
3888
3908
  async function login(email: string, password: string) {
3889
- const result = await client.login({ email, password });
3909
+ const result = await client.loginCustomer(email, password);
3890
3910
  if (result.requiresVerification) {
3891
3911
  return { state: 'needs-verification' as const };
3892
3912
  }
@@ -3921,25 +3941,37 @@ async function submitReset(token: string, newPassword: string) {
3921
3941
  "oauth-redirect-and-callback": `// OAuth sign-in. Redirect flow, NOT popup.
3922
3942
  import { client } from './brainerce';
3923
3943
 
3924
- // On your login / register UI:
3925
- const providers = await client.getAvailableOAuthProviders();
3926
- // Render one button per provider. On click, redirect the browser:
3927
- for (const p of providers) {
3928
- // button.onclick = () => window.location.assign(p.authorizationUrl)
3944
+ // Step 1: Get available provider names (strings: 'GOOGLE', 'FACEBOOK', 'GITHUB')
3945
+ const { providers } = await client.getAvailableOAuthProviders();
3946
+
3947
+ // Step 2: For each provider, fetch the authorization URL, then redirect.
3948
+ // Call this when the user clicks a provider button:
3949
+ async function redirectToOAuth(provider: string) {
3950
+ const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
3951
+ redirectUrl: \`\${window.location.origin}/auth/callback\`,
3952
+ });
3953
+ window.location.href = authorizationUrl; // full-page redirect, NOT a popup
3929
3954
  }
3930
3955
 
3931
- // On your OAuth callback route (SERVER-SIDE / BFF):
3932
- // 1. Read token + oauth_success (or oauth_error) from query params.
3933
- // 2. If oauth_error: redirect to login with the error message.
3934
- // 3. If oauth_success: exchange the token for a session cookie (your BFF),
3935
- // then redirect the browser to /account or wherever.
3936
- // Do NOT allow the token to land in browser history as a query param.`,
3956
+ // Step 3: On your /auth/callback route, read token from URL params:
3957
+ const params = new URLSearchParams(window.location.search);
3958
+ const oauthError = params.get('oauth_error');
3959
+ const token = params.get('token');
3960
+ if (oauthError) {
3961
+ window.location.href = '/login?error=' + encodeURIComponent(oauthError);
3962
+ } else if (token) {
3963
+ client.setCustomerToken(token);
3964
+ window.location.href = '/account';
3965
+ }`,
3937
3966
  "coupon-apply-and-remove": `// Coupon input. Build the UI even if no coupons exist today \u2014 it auto-hides.
3967
+ // IMPORTANT: use applyCheckoutCoupon when a checkoutId is available (checkout page).
3968
+ // Using applyCoupon after checkout creation does NOT update the checkout total.
3938
3969
  import { client } from './brainerce';
3939
3970
 
3940
- async function applyCoupon(code: string) {
3971
+ // On cart page (no checkout session yet)
3972
+ async function applyCouponToCart(cartId: string, code: string) {
3941
3973
  try {
3942
- const cart = await client.applyCoupon(code);
3974
+ const cart = await client.applyCoupon(cartId, code);
3943
3975
  return { ok: true, cart };
3944
3976
  } catch (err) {
3945
3977
  // Invalid / expired / minimum not met. Surface the specific error.
@@ -3947,17 +3979,30 @@ async function applyCoupon(code: string) {
3947
3979
  }
3948
3980
  }
3949
3981
 
3950
- async function removeCoupon() {
3951
- const cart = await client.removeCoupon();
3952
- return cart;
3982
+ async function removeCouponFromCart(cartId: string) {
3983
+ return client.removeCoupon(cartId);
3984
+ }
3985
+
3986
+ // On checkout page (checkout session already exists \u2014 always prefer this)
3987
+ async function applyCouponToCheckout(checkoutId: string, code: string) {
3988
+ try {
3989
+ const checkout = await client.applyCheckoutCoupon(checkoutId, code);
3990
+ return { ok: true, checkout }; // checkout.total is already updated
3991
+ } catch (err) {
3992
+ return { ok: false, error: (err as Error).message };
3993
+ }
3994
+ }
3995
+
3996
+ async function removeCouponFromCheckout(checkoutId: string) {
3997
+ return client.removeCheckoutCoupon(checkoutId);
3953
3998
  }`,
3954
3999
  "reservation-countdown": `// Reservation countdown. Use the SDK's expiry timestamp \u2014 do NOT invent
3955
4000
  // your own timer state.
3956
4001
  import { client } from './brainerce';
3957
4002
 
3958
- function getRemainingMs(cart: { reservationExpiresAt?: string | null }): number {
3959
- if (!cart.reservationExpiresAt) return 0;
3960
- const expiry = new Date(cart.reservationExpiresAt).getTime();
4003
+ function getRemainingMs(cart: { reservation?: { expiresAt?: string } }): number {
4004
+ if (!cart.reservation?.expiresAt) return 0;
4005
+ const expiry = new Date(cart.reservation.expiresAt).getTime();
3961
4006
  return Math.max(0, expiry - Date.now());
3962
4007
  }
3963
4008
 
@@ -3984,8 +4029,8 @@ function onSearchChange(query: string, onResults: (items: unknown[]) => void) {
3984
4029
  return;
3985
4030
  }
3986
4031
  searchTimer = setTimeout(async () => {
3987
- const results = await client.searchProducts(query);
3988
- onResults(results);
4032
+ const { products } = await client.getSearchSuggestions(query);
4033
+ onResults(products);
3989
4034
  }, 300);
3990
4035
  }`,
3991
4036
  "i18n-set-locale": `// i18n \u2014 only if get-store-capabilities reports i18n.enabled.
@@ -4342,19 +4387,13 @@ import { client } from './brainerce-client';
4342
4387
 
4343
4388
  interface CheckoutOptions {
4344
4389
  cartId: string;
4345
- shippingAddress: object;
4346
- shippingMethodId: string;
4347
- paymentProviderId: string;
4348
4390
  }
4349
4391
 
4350
4392
  async function createCheckoutSafe(opts: CheckoutOptions) {
4351
4393
  try {
4352
- return await client.createCheckout({
4353
- cartId: opts.cartId,
4354
- shippingAddress: opts.shippingAddress,
4355
- shippingMethodId: opts.shippingMethodId,
4356
- paymentProviderId: opts.paymentProviderId,
4357
- });
4394
+ // createCheckout only takes cartId (+ optional customerId, selectedItemIds).
4395
+ // Shipping address, shipping method, and payment are set via separate calls.
4396
+ return await client.createCheckout({ cartId: opts.cartId });
4358
4397
  } catch (err: unknown) {
4359
4398
  if (!isApiError(err, 'PRICE_DRIFT')) throw err;
4360
4399
 
@@ -5029,21 +5068,21 @@ var FLOWS = {
5029
5068
  1. **Collect address + email.** Build a form that captures customer email, billing address, shipping address (with \`line1\`, \`line2\`, \`city\`, \`region\`, \`postalCode\`, \`country\`). \`email\` is REQUIRED on \`SetShippingAddressDto\`.
5030
5069
  2. **Submit the address to get shipping rates.**
5031
5070
  \`\`\`ts
5032
- const { shippingRates } = await client.setShippingAddress(checkoutId, {
5071
+ const { checkout, rates } = await client.setShippingAddress(checkoutId, {
5033
5072
  email,
5034
5073
  firstName,
5035
5074
  lastName,
5036
5075
  line1, line2, city, region, postalCode, country,
5037
5076
  });
5038
5077
  \`\`\`
5039
- The response includes the available shipping rates for that address + the store's zones.
5078
+ \`rates\` = available shipping rates for that address + the store's zones. \`checkout\` = updated checkout object.
5040
5079
  3. **Let the customer pick a rate**, then persist the selection:
5041
5080
  \`\`\`ts
5042
- await client.setShippingMethod(checkoutId, rateId);
5081
+ await client.selectShippingMethod(checkoutId, rateId);
5043
5082
  \`\`\`
5044
- 4. **Fetch payment providers for this checkout:**
5083
+ 4. **Fetch payment providers:**
5045
5084
  \`\`\`ts
5046
- const providers = await client.getPaymentProviders(checkoutId);
5085
+ const providers = await client.getPaymentProviders();
5047
5086
  \`\`\`
5048
5087
  The response tells you which providers are configured (Stripe, Grow, PayPal, Sandbox) and how to render each. Each provider has a \`renderType\` telling you whether to show a Stripe Elements form, a redirect button, a PayPal button, or a sandbox "complete test order" button.
5049
5088
  5. **Confirm payment using the provider's recommended flow.** For Stripe: Stripe Elements \u2192 \`stripe.confirmCardPayment\` using the clientSecret returned by the SDK. For sandbox payments: call \`completeGuestCheckout(checkoutId)\` directly. For PayPal/Grow: follow the redirect and handle the return on your confirmation page.
@@ -5059,28 +5098,28 @@ Never bypass these steps. Never call \`submitGuestOrder\` / \`createOrder\` \u20
5059
5098
  "auth-register": {
5060
5099
  title: "Registration",
5061
5100
  body: `1. **Collect** email, password, first name, last name. Enforce strong passwords client-side: 8+ chars, upper, lower, number, special.
5062
- 2. **Call register:**
5101
+ 2. **Call registerCustomer:**
5063
5102
  \`\`\`ts
5064
- const result = await client.register({ email, password, firstName, lastName });
5103
+ const result = await client.registerCustomer({ email, password, firstName, lastName });
5065
5104
  \`\`\`
5066
5105
  3. **Branch on \`result.requiresVerification\`:**
5067
- - If \`true\`: route the user to your verify-email UI. Do NOT treat them as logged in yet.
5068
- - If \`false\`: the user is logged in. Store the token via your BFF session and route to the account area.
5106
+ - If \`true\`: store the token temporarily (e.g. sessionStorage), route the user to your verify-email UI. Do NOT treat them as logged in yet.
5107
+ - If \`false\`: call \`client.setCustomerToken(result.token)\` and route to the account area.
5069
5108
  4. **On the verify-email step:** collect a 6-digit code and call \`client.verifyEmail(code)\`. Offer a "resend code" button wired to \`client.resendVerificationEmail()\`.
5070
- 5. **After verifyEmail resolves:** the user is now logged in. Persist the session via your BFF and route to the account area.
5109
+ 5. **After verifyEmail resolves:** call \`client.setCustomerToken(result.token)\` \u2014 the user is now logged in \u2014 then route to the account area.
5071
5110
 
5072
5111
  Build the verify-email step EVEN IF the store currently has verification disabled. It auto-hides; store owners enable it later.`
5073
5112
  },
5074
5113
  "auth-login": {
5075
5114
  title: "Login",
5076
5115
  body: `1. **Collect** email + password.
5077
- 2. **Call login:**
5116
+ 2. **Call loginCustomer:**
5078
5117
  \`\`\`ts
5079
- const result = await client.login({ email, password });
5118
+ const result = await client.loginCustomer(email, password);
5080
5119
  \`\`\`
5081
5120
  3. **Branch on \`result.requiresVerification\`:**
5082
5121
  - If \`true\`: route to verify-email. The user must complete verification before accessing account features.
5083
- - If \`false\`: persist the session via your BFF and route to the previous page (or account area).
5122
+ - If \`false\`: call \`client.setCustomerToken(result.token)\` and route to the previous page (or account area).
5084
5123
  4. **Offer OAuth buttons** from \`client.getAvailableOAuthProviders()\`. Render a placeholder region even when no providers are returned \u2014 the region auto-hides today and shows buttons the moment a provider is enabled in the dashboard.
5085
5124
  5. **On error**, render the specific message (invalid credentials, rate limited, account disabled) \u2014 never swallow.`
5086
5125
  },
@@ -5105,15 +5144,24 @@ Build both steps EVEN IF the store has no email provider configured today \u2014
5105
5144
  },
5106
5145
  oauth: {
5107
5146
  title: "OAuth sign-in",
5108
- body: `1. **Render available providers:**
5147
+ body: `1. **Get available provider names:**
5148
+ \`\`\`ts
5149
+ const { providers } = await client.getAvailableOAuthProviders();
5150
+ // providers = ['GOOGLE', 'FACEBOOK', 'GITHUB'] (strings, not objects with authorizationUrl)
5151
+ \`\`\`
5152
+ 2. **For each provider, fetch the authorization URL:**
5153
+ \`\`\`ts
5154
+ const { authorizationUrl } = await client.getOAuthAuthorizeUrl(provider, {
5155
+ redirectUrl: \`\${window.location.origin}/auth/callback\`,
5156
+ });
5157
+ window.location.href = authorizationUrl; // full-page redirect, NOT a popup
5158
+ \`\`\`
5159
+ 3. **On the callback page** the URL contains \`token\` + \`oauth_success\` (or \`oauth_error\`) query params. Extract and apply the token:
5109
5160
  \`\`\`ts
5110
- const providers = await client.getAvailableOAuthProviders();
5161
+ const token = new URLSearchParams(location.search).get('token');
5162
+ if (token) client.setCustomerToken(token); // then redirect to account
5111
5163
  \`\`\`
5112
- Each provider has \`{ provider: 'GOOGLE' | 'FACEBOOK' | 'GITHUB', authorizationUrl }\`.
5113
- 2. **Build a button for each** that redirects the browser to \`authorizationUrl\`. Do NOT open a popup; the full-page redirect is required.
5114
- 3. **On the provider's side** the user authenticates, and the provider redirects back to your OAuth callback URL with \`token\` + \`oauth_success\` (or \`oauth_error\`) query params.
5115
- 4. **The callback handler MUST run server-side (BFF).** Extract the token from the query params, exchange it for a session cookie via your BFF, then redirect the browser to the account area. Never let the token land in browser history as a query param or localStorage.
5116
- 5. **On \`oauth_error\`:** redirect to login with an error message.
5164
+ 4. **On \`oauth_error\`:** redirect to login with an error message.
5117
5165
 
5118
5166
  Build the OAuth button region AND the callback handler even when no providers are configured. They auto-hide.`
5119
5167
  },
@@ -5135,7 +5183,7 @@ Build the OAuth button region AND the callback handler even when no providers ar
5135
5183
 
5136
5184
  - **Cart ID persistence:** the SDK stores the cart ID across reloads. You do not need to write cart-to-localStorage code yourself.
5137
5185
  - **Reads:** \`client.getCart()\` returns the current cart. Call it on mount in your cart UI and on any page that shows a cart count (header).
5138
- - **Writes:** use \`client.addToCart\`, \`client.updateCartItem\`, \`client.removeCartItem\`, \`client.applyCoupon\`, \`client.removeCoupon\`. After each mutation the SDK returns the updated cart.
5186
+ - **Writes:** use \`client.addToCart\`, \`client.updateCartItem\`, \`client.removeCartItem\`, \`client.applyCoupon\`, \`client.removeCoupon\`. After each mutation the SDK returns the updated cart. On the **checkout page** use \`client.applyCheckoutCoupon(checkoutId, code)\` / \`client.removeCheckoutCoupon(checkoutId)\` \u2014 these update checkout totals atomically. Never use \`applyCoupon\` after a checkout session exists.
5139
5187
  - **Totals:** call \`getCartTotals(cart)\` \u2014 do NOT read \`cart.total\`. The helper understands taxes, shipping, and discounts.
5140
5188
  - **\`smartGetCart()\`** returns \`CartWithIncludes\` (extends \`Cart\`). All carts are server-side. Pass \`{ include: ['recommendations', 'upgrades', 'bundles'] }\` to fetch extras in one request.
5141
5189
  - **NEVER mutate cart state outside SDK helpers.** Any hand-rolled cart update risks desync with the reservation timer and the checkout flow.`
@@ -5267,7 +5315,7 @@ var FEATURES = [
5267
5315
  id: "browse-products",
5268
5316
  title: "Browse, filter, and search products",
5269
5317
  description: "Users can list products with pagination, apply category / price / attribute / custom-field filters, sort by name / price / newest, and run a search with autocomplete. Custom-field filters surface metafield definitions whose filterable=true (SELECT, MULTI_SELECT, BOOLEAN). Must handle empty states and loading states.",
5270
- sdk: "client.getProducts({ page, limit, filters, sort, metafields }), client.getPublicMetafieldDefinitions(), client.searchProducts(query)",
5318
+ sdk: "client.getProducts({ page, limit, filters, sort, metafields }), client.getPublicMetafieldDefinitions(), client.getSearchSuggestions(query)",
5271
5319
  mandatory: "mandatory"
5272
5320
  },
5273
5321
  {
@@ -5297,7 +5345,7 @@ var FEATURES = [
5297
5345
  id: "cart-coupons",
5298
5346
  title: "Apply and remove coupon codes",
5299
5347
  description: "Users can enter a coupon code, see it applied to the cart, see the discount amount, and remove it. Build the UI even if no coupons are configured today \u2014 it auto-hides.",
5300
- sdk: "client.applyCoupon(code), client.removeCoupon()",
5348
+ sdk: "client.applyCoupon(cartId, code), client.removeCoupon(cartId) \u2014 on cart page. client.applyCheckoutCoupon(checkoutId, code), client.removeCheckoutCoupon(checkoutId) \u2014 on checkout page (use when checkoutId exists)",
5301
5349
  mandatory: "mandatory",
5302
5350
  capabilityFlag: "hasCoupons",
5303
5351
  whenDisabledNote: "Store has no coupons configured today. Build the UI anyway \u2014 it auto-hides until the store owner creates a coupon."
@@ -5314,7 +5362,7 @@ var FEATURES = [
5314
5362
  id: "checkout",
5315
5363
  title: "Complete a full checkout end-to-end",
5316
5364
  description: "Users can enter their address, pick a shipping rate, pick a payment provider, pay, and land on a confirmation page showing the real order. Displays checkout.lineItems (not cart.items) on the summary.",
5317
- sdk: "client.setShippingAddress, client.setShippingMethod, client.getPaymentProviders, provider-specific confirm, client.handlePaymentSuccess, client.waitForOrder",
5365
+ sdk: "client.setShippingAddress (returns { checkout, rates }), client.selectShippingMethod, client.getPaymentProviders(), provider-specific confirm, client.handlePaymentSuccess, client.waitForOrder",
5318
5366
  flowRef: "checkout",
5319
5367
  mandatory: "mandatory"
5320
5368
  },
@@ -5330,7 +5378,7 @@ var FEATURES = [
5330
5378
  id: "register",
5331
5379
  title: "Register a new account with email verification",
5332
5380
  description: 'Users can create an account (email, password, first + last name). Handles the requiresVerification branch by routing to an email-verification step. Verification step collects a 6-digit code and has a "resend code" action.',
5333
- sdk: "client.register({email, password, firstName, lastName}), client.verifyEmail(code), client.resendVerificationEmail()",
5381
+ sdk: "client.registerCustomer({email, password, firstName, lastName}), client.verifyEmail(code), client.resendVerificationEmail()",
5334
5382
  flowRef: "auth-register",
5335
5383
  mandatory: "mandatory",
5336
5384
  capabilityFlag: "oauth",
@@ -5340,7 +5388,7 @@ var FEATURES = [
5340
5388
  id: "login",
5341
5389
  title: "Log in with email / password and handle verification branch",
5342
5390
  description: "Users can log in. The login form handles the requiresVerification branch by routing to the verify-email step. Specific errors render (bad credentials, rate limited, disabled account).",
5343
- sdk: "client.login({email, password})",
5391
+ sdk: "client.loginCustomer(email, password)",
5344
5392
  flowRef: "auth-login",
5345
5393
  mandatory: "mandatory"
5346
5394
  },
@@ -5373,14 +5421,14 @@ var FEATURES = [
5373
5421
  id: "header",
5374
5422
  title: "Global header with cart count and search",
5375
5423
  description: "A header visible across the experience with the store logo, navigation, a cart icon with live item count, a search input with autocomplete (debounced ~300ms, 2-char minimum), and a login / account action. Below the header, discount banners render when active.",
5376
- sdk: "client.getCart() for the count, client.searchProducts(query) for autocomplete",
5424
+ sdk: "client.getCart() for the count, client.getSearchSuggestions(query) for autocomplete",
5377
5425
  mandatory: "mandatory"
5378
5426
  },
5379
5427
  {
5380
5428
  id: "discount-banners",
5381
5429
  title: "Show active discount banners and badges",
5382
5430
  description: "Active discount rules render as banners (below the header) and as badges on product cards / detail pages. Build the UI even if the store has no active discounts today \u2014 it auto-hides.",
5383
- sdk: "client.getDiscountBanners(), getProductDiscountBadge(product)",
5431
+ sdk: "client.getDiscountBanners(), client.getProductDiscountBadge(productId)",
5384
5432
  mandatory: "mandatory",
5385
5433
  capabilityFlag: "hasDiscountRules",
5386
5434
  whenDisabledNote: "Store has no discount rules active. Build the banner and badge components anyway \u2014 they auto-hide."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainerce/mcp-server",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "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.",
5
5
  "bin": {
6
6
  "brainerce-mcp": "dist/bin/stdio.js"