@brainerce/mcp-server 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -403,6 +403,234 @@ function PayPalPaymentForm({ clientId, orderId, checkoutId }: { clientId: string
403
403
  }
404
404
  \`\`\``;
405
405
  }
406
+ function getCheckoutCustomFieldsSection() {
407
+ return `## Checkout Custom Fields (Optional Step Between Shipping & Payment)
408
+
409
+ Some stores configure **custom fields** at checkout \u2014 e.g., gift wrapping, floor number for delivery, installation date, custom engraving. Each field can add a **surcharge** to the order total. The server filters by visibility (always / shipping-only / pickup-only / cart-products) and calculates surcharges automatically.
410
+
411
+ ### When to Build This
412
+
413
+ Check capabilities first:
414
+ \`\`\`typescript
415
+ const caps = await fetch(\`/api/vc/\${connectionId}/capabilities\`).then(r => r.json());
416
+ if (caps.features.hasCheckoutCustomFields) {
417
+ // Build the step described below
418
+ }
419
+ \`\`\`
420
+
421
+ If \`hasCheckoutCustomFields\` is true, you MUST build this step or customers will be charged surcharges they never saw.
422
+
423
+ ### Where to Place It
424
+
425
+ After \`selectShippingMethod()\` (or pickup confirmation), BEFORE \`createPaymentIntent()\`. Pseudocode:
426
+
427
+ \`\`\`
428
+ address \u2192 shipping/pickup \u2192 custom-fields (NEW) \u2192 payment
429
+ \`\`\`
430
+
431
+ If the loaded fields array is empty for this checkout (visibility filters can hide all fields), **skip the step entirely** \u2014 go straight to payment with no UI flicker.
432
+
433
+ ### Loading Field Definitions
434
+
435
+ \`\`\`typescript
436
+ import type { CheckoutCustomFieldDefinition } from 'brainerce';
437
+
438
+ const fields = await client.getCheckoutCustomFields(checkoutId);
439
+ if (fields.length === 0) {
440
+ setStep('payment'); // Nothing to collect
441
+ return;
442
+ }
443
+ \`\`\`
444
+
445
+ Each \`CheckoutCustomFieldDefinition\` has:
446
+ - \`key\` \u2014 the form field name to use in your state
447
+ - \`name\` \u2014 display label
448
+ - \`description\` \u2014 helper text (render below the input)
449
+ - \`type\` \u2014 \`'TEXT' | 'TEXTAREA' | 'NUMBER' | 'BOOLEAN' | 'SELECT' | 'DATE'\`
450
+ - \`required\` \u2014 boolean
451
+ - \`minLength\` / \`maxLength\` \u2014 for TEXT/TEXTAREA
452
+ - \`minValue\` / \`maxValue\` \u2014 for NUMBER
453
+ - \`options\` \u2014 \`Array<{value, label}>\` for SELECT
454
+ - \`pricing\` \u2014 never render this; the server uses it to compute surcharges
455
+
456
+ ### Rendering All 6 Field Types
457
+
458
+ \`\`\`typescript
459
+ function CustomFieldsStep({ fields, values, onChange, onApply, loading }: {
460
+ fields: CheckoutCustomFieldDefinition[];
461
+ values: Record<string, unknown>;
462
+ onChange: (key: string, value: unknown) => void;
463
+ onApply: () => void;
464
+ loading?: boolean;
465
+ }) {
466
+ return (
467
+ <div className="space-y-4">
468
+ {fields.map((field) => {
469
+ const value = values[field.key] ?? '';
470
+ const label = (
471
+ <label className="block text-sm font-medium">
472
+ {field.name}{field.required && <span className="text-red-500"> *</span>}
473
+ </label>
474
+ );
475
+ const help = field.description && (
476
+ <p className="text-xs text-gray-500 mt-1">{field.description}</p>
477
+ );
478
+
479
+ switch (field.type) {
480
+ case 'TEXT':
481
+ return (
482
+ <div key={field.key}>
483
+ {label}
484
+ <input
485
+ type="text"
486
+ value={value as string}
487
+ onChange={(e) => onChange(field.key, e.target.value)}
488
+ required={field.required}
489
+ minLength={field.minLength ?? undefined}
490
+ maxLength={field.maxLength ?? undefined}
491
+ className="w-full border rounded px-3 py-2"
492
+ />
493
+ {help}
494
+ </div>
495
+ );
496
+ case 'TEXTAREA':
497
+ return (
498
+ <div key={field.key}>
499
+ {label}
500
+ <textarea
501
+ value={value as string}
502
+ onChange={(e) => onChange(field.key, e.target.value)}
503
+ required={field.required}
504
+ minLength={field.minLength ?? undefined}
505
+ maxLength={field.maxLength ?? undefined}
506
+ rows={3}
507
+ className="w-full border rounded px-3 py-2"
508
+ />
509
+ {help}
510
+ </div>
511
+ );
512
+ case 'NUMBER':
513
+ return (
514
+ <div key={field.key}>
515
+ {label}
516
+ <input
517
+ type="number"
518
+ value={value as number}
519
+ onChange={(e) => onChange(field.key, e.target.valueAsNumber)}
520
+ required={field.required}
521
+ min={field.minValue ?? undefined}
522
+ max={field.maxValue ?? undefined}
523
+ className="w-full border rounded px-3 py-2"
524
+ />
525
+ {help}
526
+ </div>
527
+ );
528
+ case 'BOOLEAN':
529
+ return (
530
+ <div key={field.key} className="flex items-start gap-2">
531
+ <input
532
+ type="checkbox"
533
+ checked={value === true}
534
+ onChange={(e) => onChange(field.key, e.target.checked)}
535
+ className="mt-1"
536
+ />
537
+ <div>{label}{help}</div>
538
+ </div>
539
+ );
540
+ case 'SELECT':
541
+ return (
542
+ <div key={field.key}>
543
+ {label}
544
+ <select
545
+ value={value as string}
546
+ onChange={(e) => onChange(field.key, e.target.value)}
547
+ required={field.required}
548
+ className="w-full border rounded px-3 py-2"
549
+ >
550
+ <option value="">\u2014 Select \u2014</option>
551
+ {field.options?.map((opt) => (
552
+ <option key={opt.value} value={opt.value}>{opt.label}</option>
553
+ ))}
554
+ </select>
555
+ {help}
556
+ </div>
557
+ );
558
+ case 'DATE':
559
+ return (
560
+ <div key={field.key}>
561
+ {label}
562
+ <input
563
+ type="date"
564
+ value={value as string}
565
+ onChange={(e) => onChange(field.key, e.target.value)}
566
+ required={field.required}
567
+ className="w-full border rounded px-3 py-2"
568
+ />
569
+ {help}
570
+ </div>
571
+ );
572
+ default:
573
+ return null;
574
+ }
575
+ })}
576
+
577
+ <button
578
+ type="button"
579
+ onClick={onApply}
580
+ disabled={loading}
581
+ className="w-full bg-black text-white py-3 rounded"
582
+ >
583
+ {loading ? 'Updating\u2026' : 'Continue to Payment'}
584
+ </button>
585
+ </div>
586
+ );
587
+ }
588
+ \`\`\`
589
+
590
+ ### Submitting Values & Recalculating Surcharges
591
+
592
+ \`\`\`typescript
593
+ async function applyCustomFields() {
594
+ setLoading(true);
595
+ try {
596
+ // Server validates required + min/max + coerces types + recalculates totals
597
+ const updated = await client.setCheckoutCustomFields(checkoutId, customFieldValues);
598
+ setCheckout(updated); // updated.surchargeAmount and updated.appliedSurcharges are now fresh
599
+ setStep('payment');
600
+ } catch (err) {
601
+ setError(err instanceof Error ? err.message : 'Failed to save selections');
602
+ } finally {
603
+ setLoading(false);
604
+ }
605
+ }
606
+ \`\`\`
607
+
608
+ ### Order Summary \u2014 Show the Surcharges
609
+
610
+ **Critical:** if you don't render \`appliedSurcharges\`, customers will see a higher total with no explanation. Add this to your order summary above the total row:
611
+
612
+ \`\`\`typescript
613
+ {checkout.appliedSurcharges?.length > 0 && (
614
+ <>
615
+ {checkout.appliedSurcharges.map((s) => (
616
+ <div key={s.key} className="flex justify-between text-sm text-gray-600">
617
+ <span>{s.name}</span>
618
+ <span>{formatPrice(s.amount, { currency })}</span>
619
+ </div>
620
+ ))}
621
+ </>
622
+ )}
623
+ \`\`\`
624
+
625
+ \`checkout.surchargeAmount\` is a string (use \`parseFloat()\`); \`checkout.totalAmount\` already includes it.
626
+
627
+ ### Notes
628
+
629
+ - The server filters fields by \`visibility\` (\`always\` / \`shipping\` / \`pickup\` / \`products\`) \u2014 your client just renders what \`getCheckoutCustomFields()\` returns.
630
+ - The server validates \`required\`, \`minLength\`/\`maxLength\`, \`minValue\`/\`maxValue\`, and SELECT options. Don't rely on client validation alone \u2014 surface server errors with a toast.
631
+ - If \`getCheckoutCustomFields()\` returns 0 fields, skip the step entirely. Don't render an empty UI.
632
+ `;
633
+ }
406
634
  function getPaymentProvidersSection() {
407
635
  return `## Payment Provider Detection (DO THIS FIRST on checkout page!)
408
636
 
@@ -1252,6 +1480,10 @@ ${getCheckoutFlowSection(currency)}
1252
1480
 
1253
1481
  ---
1254
1482
 
1483
+ ${getCheckoutCustomFieldsSection()}
1484
+
1485
+ ---
1486
+
1255
1487
  ${getPaymentProvidersSection()}
1256
1488
 
1257
1489
  ---
@@ -1310,6 +1542,8 @@ function getSectionByTopic(topic, connectionId, currency) {
1310
1542
  return getCartSection(cur);
1311
1543
  case "checkout":
1312
1544
  return getCheckoutFlowSection(cur);
1545
+ case "checkout-custom-fields":
1546
+ return getCheckoutCustomFieldsSection();
1313
1547
  case "payment":
1314
1548
  return getPaymentProvidersSection();
1315
1549
  case "auth":
@@ -1338,7 +1572,7 @@ function getSectionByTopic(topic, connectionId, currency) {
1338
1572
  storeType: "E-commerce store"
1339
1573
  });
1340
1574
  default:
1341
- return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, payment, auth, order-confirmation, inventory, discounts, recommendations, tax, critical-rules, type-reference, admin, all`;
1575
+ return `Unknown topic: "${topic}". Available topics: setup, products, cart, checkout, checkout-custom-fields, payment, auth, order-confirmation, inventory, discounts, recommendations, tax, critical-rules, type-reference, admin, all`;
1342
1576
  }
1343
1577
  }
1344
1578
 
@@ -1351,6 +1585,7 @@ var GET_SDK_DOCS_SCHEMA = {
1351
1585
  "products",
1352
1586
  "cart",
1353
1587
  "checkout",
1588
+ "checkout-custom-fields",
1354
1589
  "payment",
1355
1590
  "auth",
1356
1591
  "order-confirmation",
@@ -3372,6 +3607,7 @@ import type {
3372
3607
  ShippingDestinations,
3373
3608
  PickupLocation,
3374
3609
  CheckoutBumpsResponse,
3610
+ CheckoutCustomFieldDefinition,
3375
3611
  } from 'brainerce';
3376
3612
  import { formatPrice } from 'brainerce';
3377
3613
  import { getClient } from '@/lib/brainerce';
@@ -3381,6 +3617,7 @@ import { ShippingStep } from '@/components/checkout/shipping-step';
3381
3617
  import { PaymentStep } from '@/components/checkout/payment-step';
3382
3618
  import { DeliveryMethodStep } from '@/components/checkout/delivery-method-step';
3383
3619
  import { PickupStep } from '@/components/checkout/pickup-step';
3620
+ import { CustomFieldsStep } from '@/components/checkout/custom-fields-step';
3384
3621
  import { TaxDisplay } from '@/components/checkout/tax-display';
3385
3622
  import { OrderBumpCard } from '@/components/checkout/order-bump-card';
3386
3623
  import { CouponInput } from '@/components/cart/coupon-input';
@@ -3389,7 +3626,7 @@ import { LoadingSpinner } from '@/components/shared/loading-spinner';
3389
3626
  import { useTranslations } from '@/lib/translations';
3390
3627
  import { cn } from '@/lib/utils';
3391
3628
 
3392
- type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'payment';
3629
+ type CheckoutStep = 'method' | 'address' | 'shipping' | 'pickup' | 'custom-fields' | 'payment';
3393
3630
 
3394
3631
  function CheckoutContent() {
3395
3632
  const searchParams = useSearchParams();
@@ -3422,6 +3659,9 @@ function CheckoutContent() {
3422
3659
  const [orderBumps, setOrderBumps] = useState<CheckoutBumpsResponse | null>(null);
3423
3660
  const [addedBumpIds, setAddedBumpIds] = useState<Set<string>>(new Set());
3424
3661
  const [bumpLoading, setBumpLoading] = useState<string | null>(null);
3662
+ const [customFields, setCustomFields] = useState<CheckoutCustomFieldDefinition[]>([]);
3663
+ const [customFieldValues, setCustomFieldValues] = useState<Record<string, unknown>>({});
3664
+ const [customFieldsLoading, setCustomFieldsLoading] = useState(false);
3425
3665
 
3426
3666
  // Check for returning from canceled payment
3427
3667
  const canceled = searchParams.get('canceled') === 'true';
@@ -3473,6 +3713,23 @@ function CheckoutContent() {
3473
3713
  const existing = await client.getCheckout(existingCheckoutId);
3474
3714
  setCheckout(existing);
3475
3715
 
3716
+ // Preload custom field definitions and any existing values so the
3717
+ // step indicator and "change options" affordance work on resume.
3718
+ client
3719
+ .getCheckoutCustomFields(existing.id)
3720
+ .then((fields) => {
3721
+ setCustomFields(fields);
3722
+ const existingValues = (
3723
+ existing as unknown as {
3724
+ customFieldValues?: Record<string, unknown> | null;
3725
+ }
3726
+ ).customFieldValues;
3727
+ if (existingValues) setCustomFieldValues(existingValues);
3728
+ })
3729
+ .catch(() => {
3730
+ setCustomFields([]);
3731
+ });
3732
+
3476
3733
  // Determine step based on checkout state
3477
3734
  const allDigital = existing.lineItems.every(
3478
3735
  (i) => (i.product as unknown as { isDownloadable?: boolean }).isDownloadable
@@ -3643,6 +3900,21 @@ function CheckoutContent() {
3643
3900
  }
3644
3901
  }
3645
3902
 
3903
+ // After shipping/pickup is set, decide whether to show the custom-fields step
3904
+ // or jump straight to payment. Returns the next step.
3905
+ async function loadCustomFieldsOrSkip(checkoutId: string): Promise<CheckoutStep> {
3906
+ try {
3907
+ const fields = await getClient().getCheckoutCustomFields(checkoutId);
3908
+ setCustomFields(fields);
3909
+ return fields.length > 0 ? 'custom-fields' : 'payment';
3910
+ } catch {
3911
+ // If the endpoint isn't available or fails, fall through to payment
3912
+ // rather than blocking the customer.
3913
+ setCustomFields([]);
3914
+ return 'payment';
3915
+ }
3916
+ }
3917
+
3646
3918
  // Handle shipping method selection
3647
3919
  async function handleShippingSelect(rateId: string) {
3648
3920
  if (!checkout) return;
@@ -3655,7 +3927,7 @@ function CheckoutContent() {
3655
3927
 
3656
3928
  const updated = await client.selectShippingMethod(checkout.id, rateId);
3657
3929
  setCheckout(updated);
3658
- setStep('payment');
3930
+ setStep(await loadCustomFieldsOrSkip(updated.id));
3659
3931
  } catch (err) {
3660
3932
  const message = err instanceof Error ? err.message : t('failedToSelectShipping');
3661
3933
  setError(message);
@@ -3664,6 +3936,23 @@ function CheckoutContent() {
3664
3936
  }
3665
3937
  }
3666
3938
 
3939
+ // Submit custom fields
3940
+ async function handleCustomFieldsApply() {
3941
+ if (!checkout) return;
3942
+ try {
3943
+ setCustomFieldsLoading(true);
3944
+ setError(null);
3945
+ const updated = await getClient().setCheckoutCustomFields(checkout.id, customFieldValues);
3946
+ setCheckout(updated);
3947
+ setStep('payment');
3948
+ } catch (err) {
3949
+ const message = err instanceof Error ? err.message : t('customFieldsFailed');
3950
+ setError(message);
3951
+ } finally {
3952
+ setCustomFieldsLoading(false);
3953
+ }
3954
+ }
3955
+
3667
3956
  // Handle delivery method selection
3668
3957
  async function handleDeliveryTypeSelect(method: 'shipping' | 'pickup') {
3669
3958
  if (!checkout) return;
@@ -3709,7 +3998,7 @@ function CheckoutContent() {
3709
3998
  phone: customerInfo.phone,
3710
3999
  });
3711
4000
  setCheckout(updated);
3712
- setStep('payment');
4001
+ setStep(await loadCustomFieldsOrSkip(updated.id));
3713
4002
  } catch (err) {
3714
4003
  const message = err instanceof Error ? err.message : t('failedToSelectPickup');
3715
4004
  setError(message);
@@ -3771,9 +4060,15 @@ function CheckoutContent() {
3771
4060
  );
3772
4061
  }
3773
4062
 
4063
+ const customFieldsStep =
4064
+ customFields.length > 0
4065
+ ? [{ key: 'custom-fields' as CheckoutStep, label: t('stepCustomFields') }]
4066
+ : [];
4067
+
3774
4068
  const steps: { key: CheckoutStep; label: string }[] = isAllDigital
3775
4069
  ? [
3776
4070
  { key: 'address', label: t('stepContactInfo') },
4071
+ ...customFieldsStep,
3777
4072
  { key: 'payment', label: t('stepPayment') },
3778
4073
  ]
3779
4074
  : pickupLocations.length > 0
@@ -3781,17 +4076,20 @@ function CheckoutContent() {
3781
4076
  ? [
3782
4077
  { key: 'method', label: t('stepMethod') },
3783
4078
  { key: 'pickup', label: t('stepPickup') },
4079
+ ...customFieldsStep,
3784
4080
  { key: 'payment', label: t('stepPayment') },
3785
4081
  ]
3786
4082
  : [
3787
4083
  { key: 'method', label: t('stepMethod') },
3788
4084
  { key: 'address', label: t('stepAddress') },
3789
4085
  { key: 'shipping', label: t('stepShipping') },
4086
+ ...customFieldsStep,
3790
4087
  { key: 'payment', label: t('stepPayment') },
3791
4088
  ]
3792
4089
  : [
3793
4090
  { key: 'address', label: t('stepAddress') },
3794
4091
  { key: 'shipping', label: t('stepShipping') },
4092
+ ...customFieldsStep,
3795
4093
  { key: 'payment', label: t('stepPayment') },
3796
4094
  ];
3797
4095
 
@@ -3990,19 +4288,54 @@ function CheckoutContent() {
3990
4288
  </div>
3991
4289
  )}
3992
4290
 
4291
+ {/* Custom Fields (optional, between shipping/pickup and payment) */}
4292
+ {step === 'custom-fields' && checkout && (
4293
+ <div>
4294
+ <div className="mb-4 flex items-center justify-between">
4295
+ <h2 className="text-foreground text-lg font-semibold">{t('customFieldsTitle')}</h2>
4296
+ <button
4297
+ type="button"
4298
+ onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4299
+ className="text-primary text-sm hover:underline"
4300
+ >
4301
+ {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4302
+ </button>
4303
+ </div>
4304
+ <CustomFieldsStep
4305
+ fields={customFields}
4306
+ values={customFieldValues}
4307
+ onChange={(key, value) =>
4308
+ setCustomFieldValues((prev) => ({ ...prev, [key]: value }))
4309
+ }
4310
+ onApply={handleCustomFieldsApply}
4311
+ loading={customFieldsLoading}
4312
+ />
4313
+ </div>
4314
+ )}
4315
+
3993
4316
  {/* Payment */}
3994
4317
  {step === 'payment' && checkout && (
3995
4318
  <div>
3996
4319
  <div className="mb-4 flex items-center justify-between">
3997
4320
  <h2 className="text-foreground text-lg font-semibold">{t('payment')}</h2>
3998
- {!isAllDigital && (
4321
+ {customFields.length > 0 ? (
3999
4322
  <button
4000
4323
  type="button"
4001
- onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4324
+ onClick={() => setStep('custom-fields')}
4002
4325
  className="text-primary text-sm hover:underline"
4003
4326
  >
4004
- {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4327
+ {t('changeOptions')}
4005
4328
  </button>
4329
+ ) : (
4330
+ !isAllDigital && (
4331
+ <button
4332
+ type="button"
4333
+ onClick={() => setStep(deliveryType === 'pickup' ? 'pickup' : 'shipping')}
4334
+ className="text-primary text-sm hover:underline"
4335
+ >
4336
+ {deliveryType === 'pickup' ? t('changePickup') : t('changeShipping')}
4337
+ </button>
4338
+ )
4006
4339
  )}
4007
4340
  </div>
4008
4341
 
@@ -4189,6 +4522,20 @@ function CheckoutContent() {
4189
4522
  taxBreakdown={checkout.taxBreakdown}
4190
4523
  />
4191
4524
 
4525
+ {/* Custom field surcharges (one line per applied surcharge) */}
4526
+ {checkout.appliedSurcharges && checkout.appliedSurcharges.length > 0 && (
4527
+ <>
4528
+ {checkout.appliedSurcharges.map((s) => (
4529
+ <div key={s.key} className="flex items-center justify-between">
4530
+ <span className="text-muted-foreground">{s.name}</span>
4531
+ <span className="text-foreground">
4532
+ {formatPrice(Number(s.amount), { currency }) as string}
4533
+ </span>
4534
+ </div>
4535
+ ))}
4536
+ </>
4537
+ )}
4538
+
4192
4539
  <div className="border-border mt-2 border-t pt-2">
4193
4540
  <div className="flex items-center justify-between">
4194
4541
  <span className="text-foreground font-semibold">{tc('total')}</span>
@@ -11244,8 +11591,11 @@ async function fetchStoreInfo(connectionId, baseUrl = KNOWN_API_URLS.production)
11244
11591
  } catch {
11245
11592
  throw new Error(`Invalid response from ${baseUrl}`);
11246
11593
  }
11594
+ const storeName = json.storeName || json.name || "My Store";
11595
+ const displayName = json.channelName || storeName;
11247
11596
  return {
11248
- name: json.name || json.storeName || "My Store",
11597
+ name: displayName,
11598
+ storeName,
11249
11599
  currency: json.currency || "USD",
11250
11600
  language: json.language || "en",
11251
11601
  ...json.i18n ? { i18n: json.i18n } : {}
@@ -11286,7 +11636,7 @@ function getCandidateApiUrls() {
11286
11636
 
11287
11637
  // src/tools/get-store-info.ts
11288
11638
  var GET_STORE_INFO_NAME = "get-store-info";
11289
- var GET_STORE_INFO_DESCRIPTION = "Fetch live store information from the Brainerce API using a connection ID. Returns the store name, currency, and language. Use this to personalize the store being built.";
11639
+ var GET_STORE_INFO_DESCRIPTION = "Fetch live store information from the Brainerce API using a connection ID. Returns the channel display name (what the user sees in their dashboard), parent store name, currency, and language. Use this to personalize the store being built \u2014 prefer the channel name for user-facing text since a single Brainerce store can have multiple sales channels.";
11290
11640
  var GET_STORE_INFO_SCHEMA = {
11291
11641
  connectionId: z5.string().describe("Vibe-coded connection ID (starts with vc_)")
11292
11642
  };
@@ -11299,7 +11649,10 @@ async function handleGetStoreInfo(args) {
11299
11649
  type: "text",
11300
11650
  text: JSON.stringify(
11301
11651
  {
11652
+ // `name` is the channel display name (from the dashboard sales-channel card);
11653
+ // falls back to storeName when a channel doesn't have its own name.
11302
11654
  name: resolved.info.name,
11655
+ storeName: resolved.info.storeName,
11303
11656
  currency: resolved.info.currency,
11304
11657
  language: resolved.info.language,
11305
11658
  connectionId: args.connectionId,
@@ -11393,7 +11746,8 @@ var GET_STORE_CAPABILITIES_SCHEMA = {
11393
11746
  };
11394
11747
  function formatCapabilities(caps) {
11395
11748
  const lines = [];
11396
- lines.push(`## Store: ${caps.store.name} (${caps.store.currency})`);
11749
+ const channelSuffix = caps.store.channelName && caps.store.channelName !== caps.store.name ? ` \u2014 Channel: "${caps.store.channelName}"` : "";
11750
+ lines.push(`## Store: ${caps.store.name} (${caps.store.currency})${channelSuffix}`);
11397
11751
  lines.push(`Language: ${caps.store.language}`);
11398
11752
  if (caps.store.i18n?.enabled) {
11399
11753
  lines.push(
@@ -11426,6 +11780,9 @@ function formatCapabilities(caps) {
11426
11780
  caps.features.hasDownloadableProducts ? "\u2705 Downloadable products available" : "\u274C No downloadable products"
11427
11781
  );
11428
11782
  lines.push(caps.features.hasCoupons ? "\u2705 Coupons available" : "\u274C No coupons");
11783
+ lines.push(
11784
+ caps.features.hasCheckoutCustomFields ? "\u2705 Checkout custom fields configured (surcharges may apply)" : "\u274C No checkout custom fields"
11785
+ );
11429
11786
  lines.push("");
11430
11787
  lines.push("## Connection Settings");
11431
11788
  lines.push(
@@ -11471,6 +11828,11 @@ function formatCapabilities(caps) {
11471
11828
  if (caps.features.hasCoupons) {
11472
11829
  suggestions.push('Add a coupon input to the cart. Use get-code-example("coupon-input").');
11473
11830
  }
11831
+ if (caps.features.hasCheckoutCustomFields) {
11832
+ suggestions.push(
11833
+ `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").`
11834
+ );
11835
+ }
11474
11836
  if (caps.connection.requireEmailVerification) {
11475
11837
  suggestions.push(
11476
11838
  'Email verification is required \u2014 ALWAYS build the verify-email page. Use get-code-example("verify-email").'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainerce/mcp-server",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "description": "MCP server for AI-powered Brainerce store building. Provides SDK docs, types, and code examples to AI tools like Lovable, Cursor, and Claude Code.",
5
5
  "bin": {
6
6
  "brainerce-mcp": "dist/bin/stdio.js"