@brainerce/mcp-server 3.12.3 → 3.12.4
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 +127 -61
- package/dist/bin/stdio.js +91 -3
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +91 -3
- package/dist/index.mjs +91 -3
- package/package.json +1 -1
package/dist/bin/http.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
|
|
4
4
|
// src/bin/http.ts
|
|
5
5
|
var import_node_http = require("http");
|
|
6
|
-
var import_node_crypto = require("crypto");
|
|
7
6
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
8
7
|
var import_sse = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
9
8
|
|
|
@@ -220,7 +219,7 @@ async function startCheckout() {
|
|
|
220
219
|
2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
|
|
221
220
|
3. Detect payment providers \u2192 \`getPaymentProviders()\`
|
|
222
221
|
4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
|
|
223
|
-
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
|
|
222
|
+
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\` \u2014 also pass \`notes\` from the **"Order notes" textarea that every checkout page should include by default** (optional field, max 2000 chars; lands on the order for the merchant)
|
|
224
223
|
6. Select shipping method \u2192 \`selectShippingMethod()\`
|
|
225
224
|
6b. (Optional) Set checkout custom fields \u2192 \`setCheckoutCustomFields()\` \u2014 surcharges auto-calculated
|
|
226
225
|
7. Create payment intent \u2192 \`createPaymentIntent()\` \u2192 returns \`{ clientSecret, provider }\`
|
|
@@ -242,6 +241,8 @@ function CheckoutPage() {
|
|
|
242
241
|
const [checkout, setCheckout] = useState<Checkout | null>(null);
|
|
243
242
|
const [error, setError] = useState<string | null>(null);
|
|
244
243
|
const [loading, setLoading] = useState(true);
|
|
244
|
+
// Optional "Order notes" textarea \u2014 render it on the checkout page by default
|
|
245
|
+
const [orderNotes, setOrderNotes] = useState('');
|
|
245
246
|
|
|
246
247
|
useEffect(() => { startCheckout(); }, []);
|
|
247
248
|
|
|
@@ -276,6 +277,7 @@ function CheckoutPage() {
|
|
|
276
277
|
postalCode: cart.shippingAddress.postalCode,
|
|
277
278
|
country: cart.shippingAddress.country,
|
|
278
279
|
phone: cart.shippingAddress.phone,
|
|
280
|
+
notes: orderNotes || undefined, // from the "Order notes" textarea \u2014 include one by default
|
|
279
281
|
});
|
|
280
282
|
setCheckout(checkoutData);
|
|
281
283
|
|
|
@@ -2126,6 +2128,7 @@ A useful order card includes ALL of the following when the data is present. Each
|
|
|
2126
2128
|
| **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
|
|
2127
2129
|
| **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
|
|
2128
2130
|
| Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
|
|
2131
|
+
| **Order note** | \`order.notes\` | The shopper's own checkout note, echoed back read-only. Render when present ("Your order note"). |
|
|
2129
2132
|
| Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
|
|
2130
2133
|
|
|
2131
2134
|
#### Rendering \`order.items[i].customizations\` by type
|
|
@@ -2151,7 +2154,6 @@ The storefront scaffolded by \`create-brainerce-store\` already implements this.
|
|
|
2151
2154
|
These fields are NOT returned to buyers by \`/customers/me/orders\`. If you see them in older examples, ignore them \u2014 they are for merchant/admin views only.
|
|
2152
2155
|
|
|
2153
2156
|
- \`order.accountId\`, \`order.storeId\`, \`order.customerId\` (already known on the request)
|
|
2154
|
-
- \`order.notes\` (internal merchant notes)
|
|
2155
2157
|
- \`order.customFieldValues\` (order-level checkout fields \u2014 merchant concept; buyers see \`items[i].customizations\`)
|
|
2156
2158
|
- \`order.appliedSurcharges\`, \`order.surchargeAmount\`, \`order.appliedRuleIds\`, \`order.downloadMeta\`, \`order.pickupLocationData\``;
|
|
2157
2159
|
}
|
|
@@ -2199,7 +2201,22 @@ function OrderConfirmation() {
|
|
|
2199
2201
|
|
|
2200
2202
|
**Common mistake:** Don't just check URL params and show success immediately!
|
|
2201
2203
|
You MUST call \`waitForOrder(checkoutId)\` to verify the order was created.
|
|
2202
|
-
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart
|
|
2204
|
+
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart.
|
|
2205
|
+
|
|
2206
|
+
### Optional: render full order details on the confirmation page
|
|
2207
|
+
|
|
2208
|
+
Want a richer thank-you page (items, shipping address, totals, the shopper's
|
|
2209
|
+
order note)? After \`waitForOrder\` succeeds, fetch the full buyer-safe order \u2014
|
|
2210
|
+
works for guests too (possession of the checkout id is the credential):
|
|
2211
|
+
|
|
2212
|
+
\`\`\`typescript
|
|
2213
|
+
const order = await client.getOrderByCheckout(checkoutId);
|
|
2214
|
+
// order.items, order.shippingAddress, order.notes, order.subtotal,
|
|
2215
|
+
// order.shippingAmount, order.taxAmount / order.taxBreakdown, order.totalAmount
|
|
2216
|
+
\`\`\`
|
|
2217
|
+
|
|
2218
|
+
Render whatever subset fits your design \u2014 same field shapes as the buyer
|
|
2219
|
+
order-history view (see "Buyer order view" section).`;
|
|
2203
2220
|
}
|
|
2204
2221
|
function getI18nSection() {
|
|
2205
2222
|
return `## Internationalization (i18n)
|
|
@@ -2529,6 +2546,8 @@ dashboard or the admin SDK).
|
|
|
2529
2546
|
|
|
2530
2547
|
\`\`\`typescript
|
|
2531
2548
|
// Publish to a specific vibe-coded site (admin mode):
|
|
2549
|
+
await admin.publishProductToSalesChannel('prod_id', 'vc_conn_id');
|
|
2550
|
+
await admin.publishCouponToSalesChannel('coupon_id', 'vc_conn_id');
|
|
2532
2551
|
await admin.publishCategoryToVibeCodedSite('cat_id', 'conn_id');
|
|
2533
2552
|
await admin.publishTagToVibeCodedSite('tag_id', 'conn_id');
|
|
2534
2553
|
await admin.publishBrandToVibeCodedSite('brand_id', 'conn_id');
|
|
@@ -3367,6 +3386,43 @@ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon e
|
|
|
3367
3386
|
\`\`\`typescript
|
|
3368
3387
|
await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
|
|
3369
3388
|
\`\`\`
|
|
3389
|
+
|
|
3390
|
+
## Referrals (when the store enables them)
|
|
3391
|
+
|
|
3392
|
+
\`getLoyaltyStatus()\` also returns \`referralCode\` (the member's share code, null when referrals are disabled or the customer isn't enrolled) and \`referralWelcomeCoupon\` (the still-unused welcome coupon this customer got for signing up via a referral link).
|
|
3393
|
+
|
|
3394
|
+
\`\`\`typescript
|
|
3395
|
+
// 1. Referrer: build a share link around their code.
|
|
3396
|
+
const { referralCode } = await client.getLoyaltyStatus();
|
|
3397
|
+
const shareUrl = \`https://your-store.com/?ref=\${referralCode}\`;
|
|
3398
|
+
|
|
3399
|
+
// 2. Landing page: PUBLIC lookup \u2014 no customerToken needed.
|
|
3400
|
+
const info = await client.getReferralInfo(refFromQuery);
|
|
3401
|
+
// { valid, referrerFirstName, reward: { name, type, value, minOrderAmount } | null }
|
|
3402
|
+
if (info.valid) {
|
|
3403
|
+
// "Jane sent you a gift: 10% off your first order"
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
// 3. Registration: pass the code \u2014 invalid codes never fail registration
|
|
3407
|
+
// (validated asynchronously server-side with fraud checks).
|
|
3408
|
+
await client.registerCustomer({ email, password, referralCode: refFromQuery });
|
|
3409
|
+
|
|
3410
|
+
// 4. Show the referee their welcome coupon after login:
|
|
3411
|
+
const { referralWelcomeCoupon } = await client.getLoyaltyStatus();
|
|
3412
|
+
if (referralWelcomeCoupon) {
|
|
3413
|
+
await client.applyCoupon(cartId, referralWelcomeCoupon.code);
|
|
3414
|
+
}
|
|
3415
|
+
\`\`\`
|
|
3416
|
+
|
|
3417
|
+
The referrer earns a points bonus after the referee's first qualifying order (held through the program's pending window, like order points). Everything server-side \u2014 no storefront earn calls.
|
|
3418
|
+
|
|
3419
|
+
## Birthday gifts (when the store enables them)
|
|
3420
|
+
|
|
3421
|
+
No dedicated calls \u2014 collect \`birthMonth\`/\`birthDay\` (1-12 / 1-31, **no year**, must be sent together) via \`updateMyProfile()\` and the platform emails a one-time gift coupon ahead of the customer's birthday.
|
|
3422
|
+
|
|
3423
|
+
\`\`\`typescript
|
|
3424
|
+
await client.updateMyProfile({ birthMonth: 4, birthDay: 17 });
|
|
3425
|
+
\`\`\`
|
|
3370
3426
|
`;
|
|
3371
3427
|
}
|
|
3372
3428
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
@@ -3891,6 +3947,7 @@ interface Checkout {
|
|
|
3891
3947
|
appliedSurcharges?: Array<{ key: string; name: string; value: unknown; amount: string }> | null;
|
|
3892
3948
|
customFieldValues?: Record<string, unknown> | null;
|
|
3893
3949
|
couponCode?: string | null;
|
|
3950
|
+
notes?: string | null; // Order-level shopper note \u2014 copied onto the order at completion
|
|
3894
3951
|
lineItems: CheckoutLineItem[]; // Use THIS for order summary, NOT cart.items!
|
|
3895
3952
|
itemCount: number;
|
|
3896
3953
|
availableShippingRates?: ShippingRate[];
|
|
@@ -3959,6 +4016,7 @@ interface SetShippingAddressDto {
|
|
|
3959
4016
|
postalCode: string;
|
|
3960
4017
|
country: string;
|
|
3961
4018
|
phone?: string;
|
|
4019
|
+
notes?: string; // Order notes textarea \u2014 include one on checkout by default! Max 2000 chars, lands on the order
|
|
3962
4020
|
}
|
|
3963
4021
|
|
|
3964
4022
|
interface CreateCheckoutDto {
|
|
@@ -4927,6 +4985,24 @@ interface LoyaltyStatus {
|
|
|
4927
4985
|
nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
|
|
4928
4986
|
progressToNextTier: number; // 0..1
|
|
4929
4987
|
pointsToNextTier: number | null;
|
|
4988
|
+
referralCode?: string | null; // member's share code; null when referrals disabled / not enrolled
|
|
4989
|
+
referralWelcomeCoupon?: { // unused welcome coupon from signing up via a referral link
|
|
4990
|
+
code: string;
|
|
4991
|
+
type: string; // 'PERCENTAGE' | 'FIXED_AMOUNT'
|
|
4992
|
+
value: number;
|
|
4993
|
+
} | null;
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
// Public referral-link lookup \u2014 NO customerToken needed (safe pre-signup)
|
|
4997
|
+
interface ReferralInfo {
|
|
4998
|
+
valid: boolean; // false for unknown/disabled codes
|
|
4999
|
+
referrerFirstName: string | null; // for "Jane sent you a gift" copy \u2014 no other PII
|
|
5000
|
+
reward: {
|
|
5001
|
+
name: string;
|
|
5002
|
+
type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
|
|
5003
|
+
value: number;
|
|
5004
|
+
minOrderAmount: number | null;
|
|
5005
|
+
} | null;
|
|
4930
5006
|
}
|
|
4931
5007
|
|
|
4932
5008
|
interface LoyaltyReward {
|
|
@@ -4957,6 +5033,9 @@ interface RedeemRewardResult {
|
|
|
4957
5033
|
// client.getAvailableRewards(): Promise<LoyaltyReward[]>
|
|
4958
5034
|
// client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
|
|
4959
5035
|
// client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
|
|
5036
|
+
// client.getReferralInfo(code: string): Promise<ReferralInfo> // PUBLIC \u2014 no customerToken
|
|
5037
|
+
// Referral signup: client.registerCustomer({ ..., referralCode })
|
|
5038
|
+
// Birthday gift data: client.updateMyProfile({ birthMonth, birthDay }) // 1-12 / 1-31, no year, both together
|
|
4960
5039
|
`;
|
|
4961
5040
|
var TYPES_BY_DOMAIN = {
|
|
4962
5041
|
products: PRODUCTS_TYPES,
|
|
@@ -6455,6 +6534,14 @@ function formatCapabilities(caps) {
|
|
|
6455
6534
|
lines.push(
|
|
6456
6535
|
caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
|
|
6457
6536
|
);
|
|
6537
|
+
if (caps.features.hasLoyaltyProgram) {
|
|
6538
|
+
lines.push(
|
|
6539
|
+
caps.features.hasReferralProgram ? "\u2705 Referral program enabled (share codes + welcome reward \u2014 storefront-mode only)" : "\u274C Referral program not enabled"
|
|
6540
|
+
);
|
|
6541
|
+
lines.push(
|
|
6542
|
+
caps.features.hasBirthdayRewards ? "\u2705 Birthday gifts enabled (set birthMonth/birthDay via updateMyProfile)" : "\u274C Birthday gifts not enabled"
|
|
6543
|
+
);
|
|
6544
|
+
}
|
|
6458
6545
|
lines.push("");
|
|
6459
6546
|
lines.push("## Connection Settings");
|
|
6460
6547
|
lines.push(
|
|
@@ -7640,69 +7727,29 @@ var SSE_KEEPALIVE_MS = 1e4;
|
|
|
7640
7727
|
var SESSION_MAX_AGE_MS = 4 * 60 * 60 * 1e3;
|
|
7641
7728
|
var NODE_ENV = process.env.NODE_ENV || "development";
|
|
7642
7729
|
var IS_DEV = NODE_ENV === "development";
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
"FATAL: MCP_AUTH_TOKENS is not set. The MCP HTTP server must run with at least one Bearer token in non-development environments.\nMint a token (e.g., `mcp_<random>`) via the dashboard and add it (comma-separated) to MCP_AUTH_TOKENS."
|
|
7647
|
-
);
|
|
7648
|
-
process.exit(1);
|
|
7649
|
-
}
|
|
7650
|
-
if (AUTH_TOKENS.length === 0 && IS_DEV) {
|
|
7651
|
-
console.warn(
|
|
7652
|
-
"WARN: MCP_AUTH_TOKENS is not set. Running in development mode \u2014 any non-empty Bearer token will be accepted."
|
|
7730
|
+
if (process.env.MCP_AUTH_TOKENS) {
|
|
7731
|
+
console.info(
|
|
7732
|
+
"NOTE: MCP_AUTH_TOKENS is set but ignored \u2014 the docs MCP server is public by design. The env var can be removed."
|
|
7653
7733
|
);
|
|
7654
7734
|
}
|
|
7655
7735
|
var ALLOWED_ORIGINS = (process.env.MCP_ALLOWED_ORIGINS || "").split(",").map((o) => o.trim()).filter((o) => o.length > 0);
|
|
7656
7736
|
var LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
|
|
7657
7737
|
var RATE_LIMIT_WINDOW_MS = parseInt(process.env.MCP_RATE_LIMIT_WINDOW_MS || "60000", 10);
|
|
7658
7738
|
var RATE_LIMIT_MAX = parseInt(process.env.MCP_RATE_LIMIT_MAX || "60", 10);
|
|
7739
|
+
var RATE_BUCKETS_MAX = parseInt(process.env.MCP_RATE_BUCKETS_MAX || "20000", 10);
|
|
7659
7740
|
var rateBuckets = /* @__PURE__ */ new Map();
|
|
7741
|
+
var MAX_SSE_SESSIONS = parseInt(process.env.MCP_MAX_SSE_SESSIONS || "500", 10);
|
|
7742
|
+
var MAX_SSE_SESSIONS_PER_IP = parseInt(process.env.MCP_MAX_SSE_SESSIONS_PER_IP || "5", 10);
|
|
7660
7743
|
var MAX_BODY_BYTES = parseInt(process.env.MCP_MAX_BODY_BYTES || String(100 * 1024), 10);
|
|
7661
|
-
function safeEqual(a, b) {
|
|
7662
|
-
if (a.length !== b.length) {
|
|
7663
|
-
const max = Math.max(a.length, b.length);
|
|
7664
|
-
const ax = Buffer.alloc(max);
|
|
7665
|
-
const bx = Buffer.alloc(max);
|
|
7666
|
-
a.copy(ax);
|
|
7667
|
-
b.copy(bx);
|
|
7668
|
-
(0, import_node_crypto.timingSafeEqual)(ax, bx);
|
|
7669
|
-
return false;
|
|
7670
|
-
}
|
|
7671
|
-
return (0, import_node_crypto.timingSafeEqual)(a, b);
|
|
7672
|
-
}
|
|
7673
|
-
function authenticate(req, res) {
|
|
7674
|
-
const header = req.headers["authorization"];
|
|
7675
|
-
if (typeof header !== "string" || !header.toLowerCase().startsWith("bearer ")) {
|
|
7676
|
-
writeJson(res, 401, {
|
|
7677
|
-
error: "Missing or malformed Authorization header. Expected: Bearer <token>."
|
|
7678
|
-
});
|
|
7679
|
-
return false;
|
|
7680
|
-
}
|
|
7681
|
-
const presented = header.slice(7).trim();
|
|
7682
|
-
if (presented.length === 0) {
|
|
7683
|
-
writeJson(res, 401, { error: "Empty Bearer token." });
|
|
7684
|
-
return false;
|
|
7685
|
-
}
|
|
7686
|
-
if (AUTH_TOKENS.length === 0 && IS_DEV) {
|
|
7687
|
-
return true;
|
|
7688
|
-
}
|
|
7689
|
-
const presentedBuf = Buffer.from(presented, "utf8");
|
|
7690
|
-
let matched = false;
|
|
7691
|
-
for (const token of AUTH_TOKENS) {
|
|
7692
|
-
if (safeEqual(presentedBuf, token)) {
|
|
7693
|
-
matched = true;
|
|
7694
|
-
}
|
|
7695
|
-
}
|
|
7696
|
-
if (!matched) {
|
|
7697
|
-
writeJson(res, 401, { error: "Invalid Bearer token." });
|
|
7698
|
-
return false;
|
|
7699
|
-
}
|
|
7700
|
-
return true;
|
|
7701
|
-
}
|
|
7702
7744
|
function clientIp(req) {
|
|
7745
|
+
const cf = req.headers["cf-connecting-ip"];
|
|
7746
|
+
if (typeof cf === "string" && cf.length > 0) {
|
|
7747
|
+
return cf.trim();
|
|
7748
|
+
}
|
|
7703
7749
|
const xff = req.headers["x-forwarded-for"];
|
|
7704
7750
|
if (typeof xff === "string" && xff.length > 0) {
|
|
7705
|
-
|
|
7751
|
+
const parts = xff.split(",");
|
|
7752
|
+
return parts[parts.length - 1].trim();
|
|
7706
7753
|
}
|
|
7707
7754
|
return req.socket.remoteAddress || "unknown";
|
|
7708
7755
|
}
|
|
@@ -7711,6 +7758,9 @@ function rateLimit(req, res) {
|
|
|
7711
7758
|
const now = Date.now();
|
|
7712
7759
|
const bucket = rateBuckets.get(ip);
|
|
7713
7760
|
if (!bucket || bucket.resetAt <= now) {
|
|
7761
|
+
if (rateBuckets.size >= RATE_BUCKETS_MAX) {
|
|
7762
|
+
rateBuckets.clear();
|
|
7763
|
+
}
|
|
7714
7764
|
rateBuckets.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
|
|
7715
7765
|
return true;
|
|
7716
7766
|
}
|
|
@@ -7834,7 +7884,6 @@ async function main() {
|
|
|
7834
7884
|
return;
|
|
7835
7885
|
}
|
|
7836
7886
|
if (!rateLimit(req, res)) return;
|
|
7837
|
-
if (!authenticate(req, res)) return;
|
|
7838
7887
|
if (req.url === "/mcp") {
|
|
7839
7888
|
try {
|
|
7840
7889
|
const server = createServer();
|
|
@@ -7853,6 +7902,19 @@ async function main() {
|
|
|
7853
7902
|
return;
|
|
7854
7903
|
}
|
|
7855
7904
|
if (req.url === "/sse" && req.method === "GET") {
|
|
7905
|
+
if (sseSessions.size >= MAX_SSE_SESSIONS) {
|
|
7906
|
+
writeJson(res, 503, { error: "Server at capacity. Try again later." });
|
|
7907
|
+
return;
|
|
7908
|
+
}
|
|
7909
|
+
const ip = clientIp(req);
|
|
7910
|
+
let ownedByIp = 0;
|
|
7911
|
+
for (const session of sseSessions.values()) {
|
|
7912
|
+
if (session.ip === ip) ownedByIp += 1;
|
|
7913
|
+
}
|
|
7914
|
+
if (ownedByIp >= MAX_SSE_SESSIONS_PER_IP) {
|
|
7915
|
+
writeJson(res, 429, { error: "Too many concurrent sessions from this address." });
|
|
7916
|
+
return;
|
|
7917
|
+
}
|
|
7856
7918
|
try {
|
|
7857
7919
|
res.setHeader("X-Accel-Buffering", "no");
|
|
7858
7920
|
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
@@ -7874,7 +7936,8 @@ async function main() {
|
|
|
7874
7936
|
transport,
|
|
7875
7937
|
res,
|
|
7876
7938
|
keepAliveTimer,
|
|
7877
|
-
createdAt: Date.now()
|
|
7939
|
+
createdAt: Date.now(),
|
|
7940
|
+
ip
|
|
7878
7941
|
});
|
|
7879
7942
|
transport.onclose = () => {
|
|
7880
7943
|
cleanupSession(sessionId);
|
|
@@ -7944,20 +8007,23 @@ ${signal} received \u2014 shutting down gracefully...`);
|
|
|
7944
8007
|
}
|
|
7945
8008
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
7946
8009
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
8010
|
+
httpServer.headersTimeout = 15e3;
|
|
8011
|
+
httpServer.requestTimeout = 6e4;
|
|
7947
8012
|
httpServer.listen(PORT, () => {
|
|
7948
8013
|
console.info(`Brainerce MCP server running on http://localhost:${PORT}`);
|
|
7949
8014
|
console.info(` Streamable HTTP: http://localhost:${PORT}/mcp`);
|
|
7950
8015
|
console.info(` Legacy SSE: http://localhost:${PORT}/sse`);
|
|
7951
8016
|
console.info(` Health check: http://localhost:${PORT}/health`);
|
|
7952
|
-
console.info(
|
|
7953
|
-
` Auth tokens: ${AUTH_TOKENS.length} configured${AUTH_TOKENS.length === 0 && IS_DEV ? " (dev: any non-empty Bearer accepted)" : ""}`
|
|
7954
|
-
);
|
|
8017
|
+
console.info(" Auth: none (public docs server)");
|
|
7955
8018
|
console.info(
|
|
7956
8019
|
` CORS: ${ALLOWED_ORIGINS.length} allow-listed origin(s)${IS_DEV ? " + localhost (dev)" : ""}`
|
|
7957
8020
|
);
|
|
7958
8021
|
console.info(
|
|
7959
8022
|
` Rate limit: ${RATE_LIMIT_MAX} req / ${Math.round(RATE_LIMIT_WINDOW_MS / 1e3)}s per IP`
|
|
7960
8023
|
);
|
|
8024
|
+
console.info(
|
|
8025
|
+
` SSE sessions: max ${MAX_SSE_SESSIONS} total, ${MAX_SSE_SESSIONS_PER_IP} per IP`
|
|
8026
|
+
);
|
|
7961
8027
|
console.info(` Max body: ${MAX_BODY_BYTES} bytes`);
|
|
7962
8028
|
});
|
|
7963
8029
|
}
|
package/dist/bin/stdio.js
CHANGED
|
@@ -217,7 +217,7 @@ async function startCheckout() {
|
|
|
217
217
|
2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
|
|
218
218
|
3. Detect payment providers \u2192 \`getPaymentProviders()\`
|
|
219
219
|
4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
|
|
220
|
-
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
|
|
220
|
+
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\` \u2014 also pass \`notes\` from the **"Order notes" textarea that every checkout page should include by default** (optional field, max 2000 chars; lands on the order for the merchant)
|
|
221
221
|
6. Select shipping method \u2192 \`selectShippingMethod()\`
|
|
222
222
|
6b. (Optional) Set checkout custom fields \u2192 \`setCheckoutCustomFields()\` \u2014 surcharges auto-calculated
|
|
223
223
|
7. Create payment intent \u2192 \`createPaymentIntent()\` \u2192 returns \`{ clientSecret, provider }\`
|
|
@@ -239,6 +239,8 @@ function CheckoutPage() {
|
|
|
239
239
|
const [checkout, setCheckout] = useState<Checkout | null>(null);
|
|
240
240
|
const [error, setError] = useState<string | null>(null);
|
|
241
241
|
const [loading, setLoading] = useState(true);
|
|
242
|
+
// Optional "Order notes" textarea \u2014 render it on the checkout page by default
|
|
243
|
+
const [orderNotes, setOrderNotes] = useState('');
|
|
242
244
|
|
|
243
245
|
useEffect(() => { startCheckout(); }, []);
|
|
244
246
|
|
|
@@ -273,6 +275,7 @@ function CheckoutPage() {
|
|
|
273
275
|
postalCode: cart.shippingAddress.postalCode,
|
|
274
276
|
country: cart.shippingAddress.country,
|
|
275
277
|
phone: cart.shippingAddress.phone,
|
|
278
|
+
notes: orderNotes || undefined, // from the "Order notes" textarea \u2014 include one by default
|
|
276
279
|
});
|
|
277
280
|
setCheckout(checkoutData);
|
|
278
281
|
|
|
@@ -2123,6 +2126,7 @@ A useful order card includes ALL of the following when the data is present. Each
|
|
|
2123
2126
|
| **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
|
|
2124
2127
|
| **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
|
|
2125
2128
|
| Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
|
|
2129
|
+
| **Order note** | \`order.notes\` | The shopper's own checkout note, echoed back read-only. Render when present ("Your order note"). |
|
|
2126
2130
|
| Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
|
|
2127
2131
|
|
|
2128
2132
|
#### Rendering \`order.items[i].customizations\` by type
|
|
@@ -2148,7 +2152,6 @@ The storefront scaffolded by \`create-brainerce-store\` already implements this.
|
|
|
2148
2152
|
These fields are NOT returned to buyers by \`/customers/me/orders\`. If you see them in older examples, ignore them \u2014 they are for merchant/admin views only.
|
|
2149
2153
|
|
|
2150
2154
|
- \`order.accountId\`, \`order.storeId\`, \`order.customerId\` (already known on the request)
|
|
2151
|
-
- \`order.notes\` (internal merchant notes)
|
|
2152
2155
|
- \`order.customFieldValues\` (order-level checkout fields \u2014 merchant concept; buyers see \`items[i].customizations\`)
|
|
2153
2156
|
- \`order.appliedSurcharges\`, \`order.surchargeAmount\`, \`order.appliedRuleIds\`, \`order.downloadMeta\`, \`order.pickupLocationData\``;
|
|
2154
2157
|
}
|
|
@@ -2196,7 +2199,22 @@ function OrderConfirmation() {
|
|
|
2196
2199
|
|
|
2197
2200
|
**Common mistake:** Don't just check URL params and show success immediately!
|
|
2198
2201
|
You MUST call \`waitForOrder(checkoutId)\` to verify the order was created.
|
|
2199
|
-
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart
|
|
2202
|
+
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart.
|
|
2203
|
+
|
|
2204
|
+
### Optional: render full order details on the confirmation page
|
|
2205
|
+
|
|
2206
|
+
Want a richer thank-you page (items, shipping address, totals, the shopper's
|
|
2207
|
+
order note)? After \`waitForOrder\` succeeds, fetch the full buyer-safe order \u2014
|
|
2208
|
+
works for guests too (possession of the checkout id is the credential):
|
|
2209
|
+
|
|
2210
|
+
\`\`\`typescript
|
|
2211
|
+
const order = await client.getOrderByCheckout(checkoutId);
|
|
2212
|
+
// order.items, order.shippingAddress, order.notes, order.subtotal,
|
|
2213
|
+
// order.shippingAmount, order.taxAmount / order.taxBreakdown, order.totalAmount
|
|
2214
|
+
\`\`\`
|
|
2215
|
+
|
|
2216
|
+
Render whatever subset fits your design \u2014 same field shapes as the buyer
|
|
2217
|
+
order-history view (see "Buyer order view" section).`;
|
|
2200
2218
|
}
|
|
2201
2219
|
function getI18nSection() {
|
|
2202
2220
|
return `## Internationalization (i18n)
|
|
@@ -2526,6 +2544,8 @@ dashboard or the admin SDK).
|
|
|
2526
2544
|
|
|
2527
2545
|
\`\`\`typescript
|
|
2528
2546
|
// Publish to a specific vibe-coded site (admin mode):
|
|
2547
|
+
await admin.publishProductToSalesChannel('prod_id', 'vc_conn_id');
|
|
2548
|
+
await admin.publishCouponToSalesChannel('coupon_id', 'vc_conn_id');
|
|
2529
2549
|
await admin.publishCategoryToVibeCodedSite('cat_id', 'conn_id');
|
|
2530
2550
|
await admin.publishTagToVibeCodedSite('tag_id', 'conn_id');
|
|
2531
2551
|
await admin.publishBrandToVibeCodedSite('brand_id', 'conn_id');
|
|
@@ -3364,6 +3384,43 @@ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon e
|
|
|
3364
3384
|
\`\`\`typescript
|
|
3365
3385
|
await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
|
|
3366
3386
|
\`\`\`
|
|
3387
|
+
|
|
3388
|
+
## Referrals (when the store enables them)
|
|
3389
|
+
|
|
3390
|
+
\`getLoyaltyStatus()\` also returns \`referralCode\` (the member's share code, null when referrals are disabled or the customer isn't enrolled) and \`referralWelcomeCoupon\` (the still-unused welcome coupon this customer got for signing up via a referral link).
|
|
3391
|
+
|
|
3392
|
+
\`\`\`typescript
|
|
3393
|
+
// 1. Referrer: build a share link around their code.
|
|
3394
|
+
const { referralCode } = await client.getLoyaltyStatus();
|
|
3395
|
+
const shareUrl = \`https://your-store.com/?ref=\${referralCode}\`;
|
|
3396
|
+
|
|
3397
|
+
// 2. Landing page: PUBLIC lookup \u2014 no customerToken needed.
|
|
3398
|
+
const info = await client.getReferralInfo(refFromQuery);
|
|
3399
|
+
// { valid, referrerFirstName, reward: { name, type, value, minOrderAmount } | null }
|
|
3400
|
+
if (info.valid) {
|
|
3401
|
+
// "Jane sent you a gift: 10% off your first order"
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
// 3. Registration: pass the code \u2014 invalid codes never fail registration
|
|
3405
|
+
// (validated asynchronously server-side with fraud checks).
|
|
3406
|
+
await client.registerCustomer({ email, password, referralCode: refFromQuery });
|
|
3407
|
+
|
|
3408
|
+
// 4. Show the referee their welcome coupon after login:
|
|
3409
|
+
const { referralWelcomeCoupon } = await client.getLoyaltyStatus();
|
|
3410
|
+
if (referralWelcomeCoupon) {
|
|
3411
|
+
await client.applyCoupon(cartId, referralWelcomeCoupon.code);
|
|
3412
|
+
}
|
|
3413
|
+
\`\`\`
|
|
3414
|
+
|
|
3415
|
+
The referrer earns a points bonus after the referee's first qualifying order (held through the program's pending window, like order points). Everything server-side \u2014 no storefront earn calls.
|
|
3416
|
+
|
|
3417
|
+
## Birthday gifts (when the store enables them)
|
|
3418
|
+
|
|
3419
|
+
No dedicated calls \u2014 collect \`birthMonth\`/\`birthDay\` (1-12 / 1-31, **no year**, must be sent together) via \`updateMyProfile()\` and the platform emails a one-time gift coupon ahead of the customer's birthday.
|
|
3420
|
+
|
|
3421
|
+
\`\`\`typescript
|
|
3422
|
+
await client.updateMyProfile({ birthMonth: 4, birthDay: 17 });
|
|
3423
|
+
\`\`\`
|
|
3367
3424
|
`;
|
|
3368
3425
|
}
|
|
3369
3426
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
@@ -3888,6 +3945,7 @@ interface Checkout {
|
|
|
3888
3945
|
appliedSurcharges?: Array<{ key: string; name: string; value: unknown; amount: string }> | null;
|
|
3889
3946
|
customFieldValues?: Record<string, unknown> | null;
|
|
3890
3947
|
couponCode?: string | null;
|
|
3948
|
+
notes?: string | null; // Order-level shopper note \u2014 copied onto the order at completion
|
|
3891
3949
|
lineItems: CheckoutLineItem[]; // Use THIS for order summary, NOT cart.items!
|
|
3892
3950
|
itemCount: number;
|
|
3893
3951
|
availableShippingRates?: ShippingRate[];
|
|
@@ -3956,6 +4014,7 @@ interface SetShippingAddressDto {
|
|
|
3956
4014
|
postalCode: string;
|
|
3957
4015
|
country: string;
|
|
3958
4016
|
phone?: string;
|
|
4017
|
+
notes?: string; // Order notes textarea \u2014 include one on checkout by default! Max 2000 chars, lands on the order
|
|
3959
4018
|
}
|
|
3960
4019
|
|
|
3961
4020
|
interface CreateCheckoutDto {
|
|
@@ -4924,6 +4983,24 @@ interface LoyaltyStatus {
|
|
|
4924
4983
|
nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
|
|
4925
4984
|
progressToNextTier: number; // 0..1
|
|
4926
4985
|
pointsToNextTier: number | null;
|
|
4986
|
+
referralCode?: string | null; // member's share code; null when referrals disabled / not enrolled
|
|
4987
|
+
referralWelcomeCoupon?: { // unused welcome coupon from signing up via a referral link
|
|
4988
|
+
code: string;
|
|
4989
|
+
type: string; // 'PERCENTAGE' | 'FIXED_AMOUNT'
|
|
4990
|
+
value: number;
|
|
4991
|
+
} | null;
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
// Public referral-link lookup \u2014 NO customerToken needed (safe pre-signup)
|
|
4995
|
+
interface ReferralInfo {
|
|
4996
|
+
valid: boolean; // false for unknown/disabled codes
|
|
4997
|
+
referrerFirstName: string | null; // for "Jane sent you a gift" copy \u2014 no other PII
|
|
4998
|
+
reward: {
|
|
4999
|
+
name: string;
|
|
5000
|
+
type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
|
|
5001
|
+
value: number;
|
|
5002
|
+
minOrderAmount: number | null;
|
|
5003
|
+
} | null;
|
|
4927
5004
|
}
|
|
4928
5005
|
|
|
4929
5006
|
interface LoyaltyReward {
|
|
@@ -4954,6 +5031,9 @@ interface RedeemRewardResult {
|
|
|
4954
5031
|
// client.getAvailableRewards(): Promise<LoyaltyReward[]>
|
|
4955
5032
|
// client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
|
|
4956
5033
|
// client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
|
|
5034
|
+
// client.getReferralInfo(code: string): Promise<ReferralInfo> // PUBLIC \u2014 no customerToken
|
|
5035
|
+
// Referral signup: client.registerCustomer({ ..., referralCode })
|
|
5036
|
+
// Birthday gift data: client.updateMyProfile({ birthMonth, birthDay }) // 1-12 / 1-31, no year, both together
|
|
4957
5037
|
`;
|
|
4958
5038
|
var TYPES_BY_DOMAIN = {
|
|
4959
5039
|
products: PRODUCTS_TYPES,
|
|
@@ -6452,6 +6532,14 @@ function formatCapabilities(caps) {
|
|
|
6452
6532
|
lines.push(
|
|
6453
6533
|
caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
|
|
6454
6534
|
);
|
|
6535
|
+
if (caps.features.hasLoyaltyProgram) {
|
|
6536
|
+
lines.push(
|
|
6537
|
+
caps.features.hasReferralProgram ? "\u2705 Referral program enabled (share codes + welcome reward \u2014 storefront-mode only)" : "\u274C Referral program not enabled"
|
|
6538
|
+
);
|
|
6539
|
+
lines.push(
|
|
6540
|
+
caps.features.hasBirthdayRewards ? "\u2705 Birthday gifts enabled (set birthMonth/birthDay via updateMyProfile)" : "\u274C Birthday gifts not enabled"
|
|
6541
|
+
);
|
|
6542
|
+
}
|
|
6455
6543
|
lines.push("");
|
|
6456
6544
|
lines.push("## Connection Settings");
|
|
6457
6545
|
lines.push(
|
package/dist/index.d.mts
CHANGED
|
@@ -77,6 +77,10 @@ interface StoreCapabilities {
|
|
|
77
77
|
hasContent?: boolean;
|
|
78
78
|
/** True when the store has an ACTIVE loyalty program. Storefront-mode only. */
|
|
79
79
|
hasLoyaltyProgram?: boolean;
|
|
80
|
+
/** True when the loyalty program has the referral program enabled. Storefront-mode only. */
|
|
81
|
+
hasReferralProgram?: boolean;
|
|
82
|
+
/** True when the loyalty program has birthday gifts enabled. */
|
|
83
|
+
hasBirthdayRewards?: boolean;
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
declare function fetchStoreCapabilities(connectionId: string, baseUrl?: string): Promise<StoreCapabilities>;
|
package/dist/index.d.ts
CHANGED
|
@@ -77,6 +77,10 @@ interface StoreCapabilities {
|
|
|
77
77
|
hasContent?: boolean;
|
|
78
78
|
/** True when the store has an ACTIVE loyalty program. Storefront-mode only. */
|
|
79
79
|
hasLoyaltyProgram?: boolean;
|
|
80
|
+
/** True when the loyalty program has the referral program enabled. Storefront-mode only. */
|
|
81
|
+
hasReferralProgram?: boolean;
|
|
82
|
+
/** True when the loyalty program has birthday gifts enabled. */
|
|
83
|
+
hasBirthdayRewards?: boolean;
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
86
|
declare function fetchStoreCapabilities(connectionId: string, baseUrl?: string): Promise<StoreCapabilities>;
|
package/dist/index.js
CHANGED
|
@@ -243,7 +243,7 @@ async function startCheckout() {
|
|
|
243
243
|
2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
|
|
244
244
|
3. Detect payment providers \u2192 \`getPaymentProviders()\`
|
|
245
245
|
4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
|
|
246
|
-
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
|
|
246
|
+
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\` \u2014 also pass \`notes\` from the **"Order notes" textarea that every checkout page should include by default** (optional field, max 2000 chars; lands on the order for the merchant)
|
|
247
247
|
6. Select shipping method \u2192 \`selectShippingMethod()\`
|
|
248
248
|
6b. (Optional) Set checkout custom fields \u2192 \`setCheckoutCustomFields()\` \u2014 surcharges auto-calculated
|
|
249
249
|
7. Create payment intent \u2192 \`createPaymentIntent()\` \u2192 returns \`{ clientSecret, provider }\`
|
|
@@ -265,6 +265,8 @@ function CheckoutPage() {
|
|
|
265
265
|
const [checkout, setCheckout] = useState<Checkout | null>(null);
|
|
266
266
|
const [error, setError] = useState<string | null>(null);
|
|
267
267
|
const [loading, setLoading] = useState(true);
|
|
268
|
+
// Optional "Order notes" textarea \u2014 render it on the checkout page by default
|
|
269
|
+
const [orderNotes, setOrderNotes] = useState('');
|
|
268
270
|
|
|
269
271
|
useEffect(() => { startCheckout(); }, []);
|
|
270
272
|
|
|
@@ -299,6 +301,7 @@ function CheckoutPage() {
|
|
|
299
301
|
postalCode: cart.shippingAddress.postalCode,
|
|
300
302
|
country: cart.shippingAddress.country,
|
|
301
303
|
phone: cart.shippingAddress.phone,
|
|
304
|
+
notes: orderNotes || undefined, // from the "Order notes" textarea \u2014 include one by default
|
|
302
305
|
});
|
|
303
306
|
setCheckout(checkoutData);
|
|
304
307
|
|
|
@@ -2149,6 +2152,7 @@ A useful order card includes ALL of the following when the data is present. Each
|
|
|
2149
2152
|
| **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
|
|
2150
2153
|
| **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
|
|
2151
2154
|
| Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
|
|
2155
|
+
| **Order note** | \`order.notes\` | The shopper's own checkout note, echoed back read-only. Render when present ("Your order note"). |
|
|
2152
2156
|
| Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
|
|
2153
2157
|
|
|
2154
2158
|
#### Rendering \`order.items[i].customizations\` by type
|
|
@@ -2174,7 +2178,6 @@ The storefront scaffolded by \`create-brainerce-store\` already implements this.
|
|
|
2174
2178
|
These fields are NOT returned to buyers by \`/customers/me/orders\`. If you see them in older examples, ignore them \u2014 they are for merchant/admin views only.
|
|
2175
2179
|
|
|
2176
2180
|
- \`order.accountId\`, \`order.storeId\`, \`order.customerId\` (already known on the request)
|
|
2177
|
-
- \`order.notes\` (internal merchant notes)
|
|
2178
2181
|
- \`order.customFieldValues\` (order-level checkout fields \u2014 merchant concept; buyers see \`items[i].customizations\`)
|
|
2179
2182
|
- \`order.appliedSurcharges\`, \`order.surchargeAmount\`, \`order.appliedRuleIds\`, \`order.downloadMeta\`, \`order.pickupLocationData\``;
|
|
2180
2183
|
}
|
|
@@ -2222,7 +2225,22 @@ function OrderConfirmation() {
|
|
|
2222
2225
|
|
|
2223
2226
|
**Common mistake:** Don't just check URL params and show success immediately!
|
|
2224
2227
|
You MUST call \`waitForOrder(checkoutId)\` to verify the order was created.
|
|
2225
|
-
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart
|
|
2228
|
+
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart.
|
|
2229
|
+
|
|
2230
|
+
### Optional: render full order details on the confirmation page
|
|
2231
|
+
|
|
2232
|
+
Want a richer thank-you page (items, shipping address, totals, the shopper's
|
|
2233
|
+
order note)? After \`waitForOrder\` succeeds, fetch the full buyer-safe order \u2014
|
|
2234
|
+
works for guests too (possession of the checkout id is the credential):
|
|
2235
|
+
|
|
2236
|
+
\`\`\`typescript
|
|
2237
|
+
const order = await client.getOrderByCheckout(checkoutId);
|
|
2238
|
+
// order.items, order.shippingAddress, order.notes, order.subtotal,
|
|
2239
|
+
// order.shippingAmount, order.taxAmount / order.taxBreakdown, order.totalAmount
|
|
2240
|
+
\`\`\`
|
|
2241
|
+
|
|
2242
|
+
Render whatever subset fits your design \u2014 same field shapes as the buyer
|
|
2243
|
+
order-history view (see "Buyer order view" section).`;
|
|
2226
2244
|
}
|
|
2227
2245
|
function getI18nSection() {
|
|
2228
2246
|
return `## Internationalization (i18n)
|
|
@@ -2552,6 +2570,8 @@ dashboard or the admin SDK).
|
|
|
2552
2570
|
|
|
2553
2571
|
\`\`\`typescript
|
|
2554
2572
|
// Publish to a specific vibe-coded site (admin mode):
|
|
2573
|
+
await admin.publishProductToSalesChannel('prod_id', 'vc_conn_id');
|
|
2574
|
+
await admin.publishCouponToSalesChannel('coupon_id', 'vc_conn_id');
|
|
2555
2575
|
await admin.publishCategoryToVibeCodedSite('cat_id', 'conn_id');
|
|
2556
2576
|
await admin.publishTagToVibeCodedSite('tag_id', 'conn_id');
|
|
2557
2577
|
await admin.publishBrandToVibeCodedSite('brand_id', 'conn_id');
|
|
@@ -3390,6 +3410,43 @@ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon e
|
|
|
3390
3410
|
\`\`\`typescript
|
|
3391
3411
|
await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
|
|
3392
3412
|
\`\`\`
|
|
3413
|
+
|
|
3414
|
+
## Referrals (when the store enables them)
|
|
3415
|
+
|
|
3416
|
+
\`getLoyaltyStatus()\` also returns \`referralCode\` (the member's share code, null when referrals are disabled or the customer isn't enrolled) and \`referralWelcomeCoupon\` (the still-unused welcome coupon this customer got for signing up via a referral link).
|
|
3417
|
+
|
|
3418
|
+
\`\`\`typescript
|
|
3419
|
+
// 1. Referrer: build a share link around their code.
|
|
3420
|
+
const { referralCode } = await client.getLoyaltyStatus();
|
|
3421
|
+
const shareUrl = \`https://your-store.com/?ref=\${referralCode}\`;
|
|
3422
|
+
|
|
3423
|
+
// 2. Landing page: PUBLIC lookup \u2014 no customerToken needed.
|
|
3424
|
+
const info = await client.getReferralInfo(refFromQuery);
|
|
3425
|
+
// { valid, referrerFirstName, reward: { name, type, value, minOrderAmount } | null }
|
|
3426
|
+
if (info.valid) {
|
|
3427
|
+
// "Jane sent you a gift: 10% off your first order"
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
// 3. Registration: pass the code \u2014 invalid codes never fail registration
|
|
3431
|
+
// (validated asynchronously server-side with fraud checks).
|
|
3432
|
+
await client.registerCustomer({ email, password, referralCode: refFromQuery });
|
|
3433
|
+
|
|
3434
|
+
// 4. Show the referee their welcome coupon after login:
|
|
3435
|
+
const { referralWelcomeCoupon } = await client.getLoyaltyStatus();
|
|
3436
|
+
if (referralWelcomeCoupon) {
|
|
3437
|
+
await client.applyCoupon(cartId, referralWelcomeCoupon.code);
|
|
3438
|
+
}
|
|
3439
|
+
\`\`\`
|
|
3440
|
+
|
|
3441
|
+
The referrer earns a points bonus after the referee's first qualifying order (held through the program's pending window, like order points). Everything server-side \u2014 no storefront earn calls.
|
|
3442
|
+
|
|
3443
|
+
## Birthday gifts (when the store enables them)
|
|
3444
|
+
|
|
3445
|
+
No dedicated calls \u2014 collect \`birthMonth\`/\`birthDay\` (1-12 / 1-31, **no year**, must be sent together) via \`updateMyProfile()\` and the platform emails a one-time gift coupon ahead of the customer's birthday.
|
|
3446
|
+
|
|
3447
|
+
\`\`\`typescript
|
|
3448
|
+
await client.updateMyProfile({ birthMonth: 4, birthDay: 17 });
|
|
3449
|
+
\`\`\`
|
|
3393
3450
|
`;
|
|
3394
3451
|
}
|
|
3395
3452
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
@@ -3914,6 +3971,7 @@ interface Checkout {
|
|
|
3914
3971
|
appliedSurcharges?: Array<{ key: string; name: string; value: unknown; amount: string }> | null;
|
|
3915
3972
|
customFieldValues?: Record<string, unknown> | null;
|
|
3916
3973
|
couponCode?: string | null;
|
|
3974
|
+
notes?: string | null; // Order-level shopper note \u2014 copied onto the order at completion
|
|
3917
3975
|
lineItems: CheckoutLineItem[]; // Use THIS for order summary, NOT cart.items!
|
|
3918
3976
|
itemCount: number;
|
|
3919
3977
|
availableShippingRates?: ShippingRate[];
|
|
@@ -3982,6 +4040,7 @@ interface SetShippingAddressDto {
|
|
|
3982
4040
|
postalCode: string;
|
|
3983
4041
|
country: string;
|
|
3984
4042
|
phone?: string;
|
|
4043
|
+
notes?: string; // Order notes textarea \u2014 include one on checkout by default! Max 2000 chars, lands on the order
|
|
3985
4044
|
}
|
|
3986
4045
|
|
|
3987
4046
|
interface CreateCheckoutDto {
|
|
@@ -4950,6 +5009,24 @@ interface LoyaltyStatus {
|
|
|
4950
5009
|
nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
|
|
4951
5010
|
progressToNextTier: number; // 0..1
|
|
4952
5011
|
pointsToNextTier: number | null;
|
|
5012
|
+
referralCode?: string | null; // member's share code; null when referrals disabled / not enrolled
|
|
5013
|
+
referralWelcomeCoupon?: { // unused welcome coupon from signing up via a referral link
|
|
5014
|
+
code: string;
|
|
5015
|
+
type: string; // 'PERCENTAGE' | 'FIXED_AMOUNT'
|
|
5016
|
+
value: number;
|
|
5017
|
+
} | null;
|
|
5018
|
+
}
|
|
5019
|
+
|
|
5020
|
+
// Public referral-link lookup \u2014 NO customerToken needed (safe pre-signup)
|
|
5021
|
+
interface ReferralInfo {
|
|
5022
|
+
valid: boolean; // false for unknown/disabled codes
|
|
5023
|
+
referrerFirstName: string | null; // for "Jane sent you a gift" copy \u2014 no other PII
|
|
5024
|
+
reward: {
|
|
5025
|
+
name: string;
|
|
5026
|
+
type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
|
|
5027
|
+
value: number;
|
|
5028
|
+
minOrderAmount: number | null;
|
|
5029
|
+
} | null;
|
|
4953
5030
|
}
|
|
4954
5031
|
|
|
4955
5032
|
interface LoyaltyReward {
|
|
@@ -4980,6 +5057,9 @@ interface RedeemRewardResult {
|
|
|
4980
5057
|
// client.getAvailableRewards(): Promise<LoyaltyReward[]>
|
|
4981
5058
|
// client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
|
|
4982
5059
|
// client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
|
|
5060
|
+
// client.getReferralInfo(code: string): Promise<ReferralInfo> // PUBLIC \u2014 no customerToken
|
|
5061
|
+
// Referral signup: client.registerCustomer({ ..., referralCode })
|
|
5062
|
+
// Birthday gift data: client.updateMyProfile({ birthMonth, birthDay }) // 1-12 / 1-31, no year, both together
|
|
4983
5063
|
`;
|
|
4984
5064
|
var TYPES_BY_DOMAIN = {
|
|
4985
5065
|
products: PRODUCTS_TYPES,
|
|
@@ -6478,6 +6558,14 @@ function formatCapabilities(caps) {
|
|
|
6478
6558
|
lines.push(
|
|
6479
6559
|
caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
|
|
6480
6560
|
);
|
|
6561
|
+
if (caps.features.hasLoyaltyProgram) {
|
|
6562
|
+
lines.push(
|
|
6563
|
+
caps.features.hasReferralProgram ? "\u2705 Referral program enabled (share codes + welcome reward \u2014 storefront-mode only)" : "\u274C Referral program not enabled"
|
|
6564
|
+
);
|
|
6565
|
+
lines.push(
|
|
6566
|
+
caps.features.hasBirthdayRewards ? "\u2705 Birthday gifts enabled (set birthMonth/birthDay via updateMyProfile)" : "\u274C Birthday gifts not enabled"
|
|
6567
|
+
);
|
|
6568
|
+
}
|
|
6481
6569
|
lines.push("");
|
|
6482
6570
|
lines.push("## Connection Settings");
|
|
6483
6571
|
lines.push(
|
package/dist/index.mjs
CHANGED
|
@@ -211,7 +211,7 @@ async function startCheckout() {
|
|
|
211
211
|
2. Customer optionally applies coupon on cart page \u2192 \`applyCoupon(cartId, code)\`
|
|
212
212
|
3. Detect payment providers \u2192 \`getPaymentProviders()\`
|
|
213
213
|
4. Start checkout session (\`startGuestCheckout()\` or \`createCheckout()\`)
|
|
214
|
-
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\`
|
|
214
|
+
5. Set shipping address (includes required email) \u2192 \`setShippingAddress()\` \u2014 also pass \`notes\` from the **"Order notes" textarea that every checkout page should include by default** (optional field, max 2000 chars; lands on the order for the merchant)
|
|
215
215
|
6. Select shipping method \u2192 \`selectShippingMethod()\`
|
|
216
216
|
6b. (Optional) Set checkout custom fields \u2192 \`setCheckoutCustomFields()\` \u2014 surcharges auto-calculated
|
|
217
217
|
7. Create payment intent \u2192 \`createPaymentIntent()\` \u2192 returns \`{ clientSecret, provider }\`
|
|
@@ -233,6 +233,8 @@ function CheckoutPage() {
|
|
|
233
233
|
const [checkout, setCheckout] = useState<Checkout | null>(null);
|
|
234
234
|
const [error, setError] = useState<string | null>(null);
|
|
235
235
|
const [loading, setLoading] = useState(true);
|
|
236
|
+
// Optional "Order notes" textarea \u2014 render it on the checkout page by default
|
|
237
|
+
const [orderNotes, setOrderNotes] = useState('');
|
|
236
238
|
|
|
237
239
|
useEffect(() => { startCheckout(); }, []);
|
|
238
240
|
|
|
@@ -267,6 +269,7 @@ function CheckoutPage() {
|
|
|
267
269
|
postalCode: cart.shippingAddress.postalCode,
|
|
268
270
|
country: cart.shippingAddress.country,
|
|
269
271
|
phone: cart.shippingAddress.phone,
|
|
272
|
+
notes: orderNotes || undefined, // from the "Order notes" textarea \u2014 include one by default
|
|
270
273
|
});
|
|
271
274
|
setCheckout(checkoutData);
|
|
272
275
|
|
|
@@ -2117,6 +2120,7 @@ A useful order card includes ALL of the following when the data is present. Each
|
|
|
2117
2120
|
| **Tracking** | \`order.trackingNumber\`, \`order.trackingUrl\`, \`order.carrier\`, \`order.shippedAt\`, \`order.deliveredAt\` | Link out to \`trackingUrl\` when set. |
|
|
2118
2121
|
| **Payment** | \`order.paymentMethod\`, \`order.financialStatus\` | Badge \`financialStatus\` (paid / pending / refunded / partially_refunded). |
|
|
2119
2122
|
| Downloads | \`order.hasDownloads\` \u2192 \`client.getOrderDownloads(id)\` | Separate call; returns \`OrderDownloadLink[]\`. |
|
|
2123
|
+
| **Order note** | \`order.notes\` | The shopper's own checkout note, echoed back read-only. Render when present ("Your order note"). |
|
|
2120
2124
|
| Financial summary | \`order.subtotal\`, \`order.appliedDiscounts\`, \`order.couponCode\` + \`couponDiscount\`, \`order.shippingAmount\`, \`order.taxAmount\`, \`order.totalAmount\` | Breakdown rows + final total. In VAT-inclusive mode \`taxAmount\` is 0 \u2014 read \`order.taxBreakdown.totalTax\` and label "Tax (incl.)". |
|
|
2121
2125
|
|
|
2122
2126
|
#### Rendering \`order.items[i].customizations\` by type
|
|
@@ -2142,7 +2146,6 @@ The storefront scaffolded by \`create-brainerce-store\` already implements this.
|
|
|
2142
2146
|
These fields are NOT returned to buyers by \`/customers/me/orders\`. If you see them in older examples, ignore them \u2014 they are for merchant/admin views only.
|
|
2143
2147
|
|
|
2144
2148
|
- \`order.accountId\`, \`order.storeId\`, \`order.customerId\` (already known on the request)
|
|
2145
|
-
- \`order.notes\` (internal merchant notes)
|
|
2146
2149
|
- \`order.customFieldValues\` (order-level checkout fields \u2014 merchant concept; buyers see \`items[i].customizations\`)
|
|
2147
2150
|
- \`order.appliedSurcharges\`, \`order.surchargeAmount\`, \`order.appliedRuleIds\`, \`order.downloadMeta\`, \`order.pickupLocationData\``;
|
|
2148
2151
|
}
|
|
@@ -2190,7 +2193,22 @@ function OrderConfirmation() {
|
|
|
2190
2193
|
|
|
2191
2194
|
**Common mistake:** Don't just check URL params and show success immediately!
|
|
2192
2195
|
You MUST call \`waitForOrder(checkoutId)\` to verify the order was created.
|
|
2193
|
-
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart
|
|
2196
|
+
Also call \`handlePaymentSuccess(checkoutId)\` to clear purchased items from the cart.
|
|
2197
|
+
|
|
2198
|
+
### Optional: render full order details on the confirmation page
|
|
2199
|
+
|
|
2200
|
+
Want a richer thank-you page (items, shipping address, totals, the shopper's
|
|
2201
|
+
order note)? After \`waitForOrder\` succeeds, fetch the full buyer-safe order \u2014
|
|
2202
|
+
works for guests too (possession of the checkout id is the credential):
|
|
2203
|
+
|
|
2204
|
+
\`\`\`typescript
|
|
2205
|
+
const order = await client.getOrderByCheckout(checkoutId);
|
|
2206
|
+
// order.items, order.shippingAddress, order.notes, order.subtotal,
|
|
2207
|
+
// order.shippingAmount, order.taxAmount / order.taxBreakdown, order.totalAmount
|
|
2208
|
+
\`\`\`
|
|
2209
|
+
|
|
2210
|
+
Render whatever subset fits your design \u2014 same field shapes as the buyer
|
|
2211
|
+
order-history view (see "Buyer order view" section).`;
|
|
2194
2212
|
}
|
|
2195
2213
|
function getI18nSection() {
|
|
2196
2214
|
return `## Internationalization (i18n)
|
|
@@ -2520,6 +2538,8 @@ dashboard or the admin SDK).
|
|
|
2520
2538
|
|
|
2521
2539
|
\`\`\`typescript
|
|
2522
2540
|
// Publish to a specific vibe-coded site (admin mode):
|
|
2541
|
+
await admin.publishProductToSalesChannel('prod_id', 'vc_conn_id');
|
|
2542
|
+
await admin.publishCouponToSalesChannel('coupon_id', 'vc_conn_id');
|
|
2523
2543
|
await admin.publishCategoryToVibeCodedSite('cat_id', 'conn_id');
|
|
2524
2544
|
await admin.publishTagToVibeCodedSite('tag_id', 'conn_id');
|
|
2525
2545
|
await admin.publishBrandToVibeCodedSite('brand_id', 'conn_id');
|
|
@@ -3358,6 +3378,43 @@ await client.applyCoupon(cartId, couponCode); // applies via the normal coupon e
|
|
|
3358
3378
|
\`\`\`typescript
|
|
3359
3379
|
await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
|
|
3360
3380
|
\`\`\`
|
|
3381
|
+
|
|
3382
|
+
## Referrals (when the store enables them)
|
|
3383
|
+
|
|
3384
|
+
\`getLoyaltyStatus()\` also returns \`referralCode\` (the member's share code, null when referrals are disabled or the customer isn't enrolled) and \`referralWelcomeCoupon\` (the still-unused welcome coupon this customer got for signing up via a referral link).
|
|
3385
|
+
|
|
3386
|
+
\`\`\`typescript
|
|
3387
|
+
// 1. Referrer: build a share link around their code.
|
|
3388
|
+
const { referralCode } = await client.getLoyaltyStatus();
|
|
3389
|
+
const shareUrl = \`https://your-store.com/?ref=\${referralCode}\`;
|
|
3390
|
+
|
|
3391
|
+
// 2. Landing page: PUBLIC lookup \u2014 no customerToken needed.
|
|
3392
|
+
const info = await client.getReferralInfo(refFromQuery);
|
|
3393
|
+
// { valid, referrerFirstName, reward: { name, type, value, minOrderAmount } | null }
|
|
3394
|
+
if (info.valid) {
|
|
3395
|
+
// "Jane sent you a gift: 10% off your first order"
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
// 3. Registration: pass the code \u2014 invalid codes never fail registration
|
|
3399
|
+
// (validated asynchronously server-side with fraud checks).
|
|
3400
|
+
await client.registerCustomer({ email, password, referralCode: refFromQuery });
|
|
3401
|
+
|
|
3402
|
+
// 4. Show the referee their welcome coupon after login:
|
|
3403
|
+
const { referralWelcomeCoupon } = await client.getLoyaltyStatus();
|
|
3404
|
+
if (referralWelcomeCoupon) {
|
|
3405
|
+
await client.applyCoupon(cartId, referralWelcomeCoupon.code);
|
|
3406
|
+
}
|
|
3407
|
+
\`\`\`
|
|
3408
|
+
|
|
3409
|
+
The referrer earns a points bonus after the referee's first qualifying order (held through the program's pending window, like order points). Everything server-side \u2014 no storefront earn calls.
|
|
3410
|
+
|
|
3411
|
+
## Birthday gifts (when the store enables them)
|
|
3412
|
+
|
|
3413
|
+
No dedicated calls \u2014 collect \`birthMonth\`/\`birthDay\` (1-12 / 1-31, **no year**, must be sent together) via \`updateMyProfile()\` and the platform emails a one-time gift coupon ahead of the customer's birthday.
|
|
3414
|
+
|
|
3415
|
+
\`\`\`typescript
|
|
3416
|
+
await client.updateMyProfile({ birthMonth: 4, birthDay: 17 });
|
|
3417
|
+
\`\`\`
|
|
3361
3418
|
`;
|
|
3362
3419
|
}
|
|
3363
3420
|
function getSectionByTopic(topic, connectionId, currency) {
|
|
@@ -3882,6 +3939,7 @@ interface Checkout {
|
|
|
3882
3939
|
appliedSurcharges?: Array<{ key: string; name: string; value: unknown; amount: string }> | null;
|
|
3883
3940
|
customFieldValues?: Record<string, unknown> | null;
|
|
3884
3941
|
couponCode?: string | null;
|
|
3942
|
+
notes?: string | null; // Order-level shopper note \u2014 copied onto the order at completion
|
|
3885
3943
|
lineItems: CheckoutLineItem[]; // Use THIS for order summary, NOT cart.items!
|
|
3886
3944
|
itemCount: number;
|
|
3887
3945
|
availableShippingRates?: ShippingRate[];
|
|
@@ -3950,6 +4008,7 @@ interface SetShippingAddressDto {
|
|
|
3950
4008
|
postalCode: string;
|
|
3951
4009
|
country: string;
|
|
3952
4010
|
phone?: string;
|
|
4011
|
+
notes?: string; // Order notes textarea \u2014 include one on checkout by default! Max 2000 chars, lands on the order
|
|
3953
4012
|
}
|
|
3954
4013
|
|
|
3955
4014
|
interface CreateCheckoutDto {
|
|
@@ -4918,6 +4977,24 @@ interface LoyaltyStatus {
|
|
|
4918
4977
|
nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
|
|
4919
4978
|
progressToNextTier: number; // 0..1
|
|
4920
4979
|
pointsToNextTier: number | null;
|
|
4980
|
+
referralCode?: string | null; // member's share code; null when referrals disabled / not enrolled
|
|
4981
|
+
referralWelcomeCoupon?: { // unused welcome coupon from signing up via a referral link
|
|
4982
|
+
code: string;
|
|
4983
|
+
type: string; // 'PERCENTAGE' | 'FIXED_AMOUNT'
|
|
4984
|
+
value: number;
|
|
4985
|
+
} | null;
|
|
4986
|
+
}
|
|
4987
|
+
|
|
4988
|
+
// Public referral-link lookup \u2014 NO customerToken needed (safe pre-signup)
|
|
4989
|
+
interface ReferralInfo {
|
|
4990
|
+
valid: boolean; // false for unknown/disabled codes
|
|
4991
|
+
referrerFirstName: string | null; // for "Jane sent you a gift" copy \u2014 no other PII
|
|
4992
|
+
reward: {
|
|
4993
|
+
name: string;
|
|
4994
|
+
type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
|
|
4995
|
+
value: number;
|
|
4996
|
+
minOrderAmount: number | null;
|
|
4997
|
+
} | null;
|
|
4921
4998
|
}
|
|
4922
4999
|
|
|
4923
5000
|
interface LoyaltyReward {
|
|
@@ -4948,6 +5025,9 @@ interface RedeemRewardResult {
|
|
|
4948
5025
|
// client.getAvailableRewards(): Promise<LoyaltyReward[]>
|
|
4949
5026
|
// client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
|
|
4950
5027
|
// client.reportSocialShare(platform?: string): Promise<{ awarded: boolean; points: number }>
|
|
5028
|
+
// client.getReferralInfo(code: string): Promise<ReferralInfo> // PUBLIC \u2014 no customerToken
|
|
5029
|
+
// Referral signup: client.registerCustomer({ ..., referralCode })
|
|
5030
|
+
// Birthday gift data: client.updateMyProfile({ birthMonth, birthDay }) // 1-12 / 1-31, no year, both together
|
|
4951
5031
|
`;
|
|
4952
5032
|
var TYPES_BY_DOMAIN = {
|
|
4953
5033
|
products: PRODUCTS_TYPES,
|
|
@@ -6446,6 +6526,14 @@ function formatCapabilities(caps) {
|
|
|
6446
6526
|
lines.push(
|
|
6447
6527
|
caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
|
|
6448
6528
|
);
|
|
6529
|
+
if (caps.features.hasLoyaltyProgram) {
|
|
6530
|
+
lines.push(
|
|
6531
|
+
caps.features.hasReferralProgram ? "\u2705 Referral program enabled (share codes + welcome reward \u2014 storefront-mode only)" : "\u274C Referral program not enabled"
|
|
6532
|
+
);
|
|
6533
|
+
lines.push(
|
|
6534
|
+
caps.features.hasBirthdayRewards ? "\u2705 Birthday gifts enabled (set birthMonth/birthDay via updateMyProfile)" : "\u274C Birthday gifts not enabled"
|
|
6535
|
+
);
|
|
6536
|
+
}
|
|
6449
6537
|
lines.push("");
|
|
6450
6538
|
lines.push("## Connection Settings");
|
|
6451
6539
|
lines.push(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainerce/mcp-server",
|
|
3
|
-
"version": "3.12.
|
|
3
|
+
"version": "3.12.4",
|
|
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": {
|