@brainerce/mcp-server 3.12.2 → 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 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');
@@ -3324,11 +3343,18 @@ Points are earned automatically by the platform when an order is paid \u2014 the
3324
3343
  \`\`\`typescript
3325
3344
  // requires client.setCustomerToken(auth.token)
3326
3345
  const status = await client.getLoyaltyStatus();
3327
- // { enrolled, pointsBalance, lifetimeEarned, program: { pointsName, currencyRatio, status } | null }
3346
+ // {
3347
+ // enrolled, pointsBalance, lifetimeEarned,
3348
+ // program: { pointsName, currencyRatio, status } | null,
3349
+ // tier: { id, name, level, pointsMultiplier } | null,
3350
+ // nextTier: { id, name, level, pointsMultiplier, qualificationType, qualificationThreshold } | null,
3351
+ // progressToNextTier, pointsToNextTier,
3352
+ // }
3328
3353
  \`\`\`
3329
3354
 
3330
3355
  - \`program === null\` \u2192 the store has no loyalty program; hide the loyalty UI.
3331
3356
  - \`program.status !== 'ACTIVE'\` \u2192 program paused/draft; hide the loyalty UI.
3357
+ - \`tier\`/\`nextTier\` are \`null\` when the store hasn't configured tiers, or the customer hasn't qualified for one yet.
3332
3358
 
3333
3359
  ## Enroll (only if auto-enroll is off)
3334
3360
 
@@ -3336,19 +3362,67 @@ const status = await client.getLoyaltyStatus();
3336
3362
  await client.enrollInLoyalty(); // same status shape; no-op if already enrolled
3337
3363
  \`\`\`
3338
3364
 
3365
+ May grant a one-time "joined the program" bonus if configured \u2014 distinct from any account-signup bonus (granted automatically on registration, not by this call).
3366
+
3339
3367
  ## Rewards & redemption
3340
3368
 
3341
3369
  \`\`\`typescript
3342
3370
  const rewards = await client.getAvailableRewards();
3343
- // [{ id, name, description, pointsCost, discountValue, minOrderAmount, maxUsesPerUser, isActive }]
3371
+ // [{ id, name, description, pointsCost, type, discountValue, minOrderAmount, maxUsesPerUser, minTierLevel, isActive }]
3372
+ // Already filtered to rewards the customer's current tier qualifies for.
3344
3373
 
3345
- const { couponCode, discountValue, pointsBalance } = await client.redeemLoyaltyReward(rewardId);
3374
+ const { couponCode, discountType, discountValue, pointsBalance } =
3375
+ await client.redeemLoyaltyReward(rewardId);
3346
3376
  await client.applyCoupon(cartId, couponCode); // applies via the normal coupon engine
3347
3377
  \`\`\`
3348
3378
 
3349
- - \`redeemLoyaltyReward\` mints a one-time fixed-amount coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3350
- - Throws if the customer has insufficient points. If minting fails, points are refunded automatically.
3379
+ - \`reward.type\` is \`'FIXED_DISCOUNT'\` (currency amount) or \`'PERCENT_DISCOUNT'\` (0-100 percent) \u2014 the redeem result's \`discountType\` matches.
3380
+ - \`redeemLoyaltyReward\` mints a one-time coupon (\`usageLimit: 1\`). Apply it with the normal \`applyCoupon\` flow.
3381
+ - Throws if the customer has insufficient points, or their tier is below \`reward.minTierLevel\`. If minting fails, points are refunded automatically.
3351
3382
  - Disable a reward's redeem button when \`pointsBalance < reward.pointsCost\`.
3383
+
3384
+ ## Social share (optional)
3385
+
3386
+ \`\`\`typescript
3387
+ await client.reportSocialShare('instagram'); // platform optional, self-reported, once per customer
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
+ \`\`\`
3352
3426
  `;
3353
3427
  }
3354
3428
  function getSectionByTopic(topic, connectionId, currency) {
@@ -3873,6 +3947,7 @@ interface Checkout {
3873
3947
  appliedSurcharges?: Array<{ key: string; name: string; value: unknown; amount: string }> | null;
3874
3948
  customFieldValues?: Record<string, unknown> | null;
3875
3949
  couponCode?: string | null;
3950
+ notes?: string | null; // Order-level shopper note \u2014 copied onto the order at completion
3876
3951
  lineItems: CheckoutLineItem[]; // Use THIS for order summary, NOT cart.items!
3877
3952
  itemCount: number;
3878
3953
  availableShippingRates?: ShippingRate[];
@@ -3941,6 +4016,7 @@ interface SetShippingAddressDto {
3941
4016
  postalCode: string;
3942
4017
  country: string;
3943
4018
  phone?: string;
4019
+ notes?: string; // Order notes textarea \u2014 include one on checkout by default! Max 2000 chars, lands on the order
3944
4020
  }
3945
4021
 
3946
4022
  interface CreateCheckoutDto {
@@ -4884,6 +4960,18 @@ interface TaxEstimateResponse {
4884
4960
  // full shipping address. The country comes from your edge runtime
4885
4961
  // (CF-IPCountry / request.geo.country / etc.), NOT the request IP.`;
4886
4962
  var LOYALTY_TYPES = `// ---- Loyalty & Rewards (storefront mode only) ----
4963
+ interface LoyaltyTierSummary {
4964
+ id: string;
4965
+ name: string;
4966
+ level: number; // ordering; higher = better tier
4967
+ pointsMultiplier: number;
4968
+ }
4969
+
4970
+ interface LoyaltyNextTierSummary extends LoyaltyTierSummary {
4971
+ qualificationType: 'SPEND' | 'POINTS';
4972
+ qualificationThreshold: number;
4973
+ }
4974
+
4887
4975
  interface LoyaltyStatus {
4888
4976
  enrolled: boolean;
4889
4977
  pointsBalance: number; // redeemable points (pending earns excluded)
@@ -4893,6 +4981,28 @@ interface LoyaltyStatus {
4893
4981
  currencyRatio: number; // points needed to redeem 1 unit of store currency
4894
4982
  status: 'DRAFT' | 'ACTIVE' | 'PAUSED';
4895
4983
  } | null; // null when the store has no loyalty program
4984
+ tier: LoyaltyTierSummary | null; // null if untiered / no tiers configured
4985
+ nextTier: LoyaltyNextTierSummary | null; // null if at the top tier already
4986
+ progressToNextTier: number; // 0..1
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;
4896
5006
  }
4897
5007
 
4898
5008
  interface LoyaltyReward {
@@ -4900,16 +5010,19 @@ interface LoyaltyReward {
4900
5010
  name: string;
4901
5011
  description: string | null;
4902
5012
  pointsCost: number;
4903
- discountValue: number; // fixed amount off, store currency
5013
+ type: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
5014
+ discountValue: number; // currency amount (FIXED_DISCOUNT) or 0-100 percent (PERCENT_DISCOUNT)
4904
5015
  minOrderAmount: number | null;
4905
5016
  maxUsesPerUser: number;
4906
5017
  isActive: boolean;
5018
+ minTierLevel: number | null; // minimum tier level required, or null for no restriction
4907
5019
  createdAt: string;
4908
5020
  updatedAt: string;
4909
5021
  }
4910
5022
 
4911
5023
  interface RedeemRewardResult {
4912
5024
  couponCode: string; // one-time coupon to apply at checkout
5025
+ discountType: 'FIXED_DISCOUNT' | 'PERCENT_DISCOUNT';
4913
5026
  discountValue: number;
4914
5027
  pointsSpent: number;
4915
5028
  pointsBalance: number; // remaining balance after redemption
@@ -4919,6 +5032,10 @@ interface RedeemRewardResult {
4919
5032
  // client.enrollInLoyalty(): Promise<LoyaltyStatus>
4920
5033
  // client.getAvailableRewards(): Promise<LoyaltyReward[]>
4921
5034
  // client.redeemLoyaltyReward(rewardId: string): Promise<RedeemRewardResult>
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
4922
5039
  `;
4923
5040
  var TYPES_BY_DOMAIN = {
4924
5041
  products: PRODUCTS_TYPES,
@@ -6417,6 +6534,14 @@ function formatCapabilities(caps) {
6417
6534
  lines.push(
6418
6535
  caps.features.hasLoyaltyProgram ? "\u2705 Loyalty program active (points & rewards \u2014 storefront-mode only)" : "\u274C No loyalty program"
6419
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
+ }
6420
6545
  lines.push("");
6421
6546
  lines.push("## Connection Settings");
6422
6547
  lines.push(
@@ -6464,7 +6589,7 @@ function formatCapabilities(caps) {
6464
6589
  }
6465
6590
  if (caps.features.hasLoyaltyProgram) {
6466
6591
  suggestions.push(
6467
- "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus(), getAvailableRewards(), and redeemLoyaltyReward(id). Note: loyalty is NOT available in vibe-coded mode."
6592
+ "A loyalty program is active. In a storefront-mode client (new BrainerceClient({ storeId })), show the logged-in customer their points balance, tier progress, and redeemable rewards, and let them redeem for a coupon at checkout. Use getLoyaltyStatus() (includes tier/nextTier/progressToNextTier), getAvailableRewards(), redeemLoyaltyReward(id), and optionally reportSocialShare(). Note: loyalty is NOT available in vibe-coded mode."
6468
6593
  );
6469
6594
  }
6470
6595
  if (caps.features.hasCheckoutCustomFields) {
@@ -7602,69 +7727,29 @@ var SSE_KEEPALIVE_MS = 1e4;
7602
7727
  var SESSION_MAX_AGE_MS = 4 * 60 * 60 * 1e3;
7603
7728
  var NODE_ENV = process.env.NODE_ENV || "development";
7604
7729
  var IS_DEV = NODE_ENV === "development";
7605
- var AUTH_TOKENS = (process.env.MCP_AUTH_TOKENS || "").split(",").map((t) => t.trim()).filter((t) => t.length > 0).map((t) => Buffer.from(t, "utf8"));
7606
- if (AUTH_TOKENS.length === 0 && !IS_DEV) {
7607
- console.error(
7608
- "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."
7609
- );
7610
- process.exit(1);
7611
- }
7612
- if (AUTH_TOKENS.length === 0 && IS_DEV) {
7613
- console.warn(
7614
- "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."
7615
7733
  );
7616
7734
  }
7617
7735
  var ALLOWED_ORIGINS = (process.env.MCP_ALLOWED_ORIGINS || "").split(",").map((o) => o.trim()).filter((o) => o.length > 0);
7618
7736
  var LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
7619
7737
  var RATE_LIMIT_WINDOW_MS = parseInt(process.env.MCP_RATE_LIMIT_WINDOW_MS || "60000", 10);
7620
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);
7621
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);
7622
7743
  var MAX_BODY_BYTES = parseInt(process.env.MCP_MAX_BODY_BYTES || String(100 * 1024), 10);
7623
- function safeEqual(a, b) {
7624
- if (a.length !== b.length) {
7625
- const max = Math.max(a.length, b.length);
7626
- const ax = Buffer.alloc(max);
7627
- const bx = Buffer.alloc(max);
7628
- a.copy(ax);
7629
- b.copy(bx);
7630
- (0, import_node_crypto.timingSafeEqual)(ax, bx);
7631
- return false;
7632
- }
7633
- return (0, import_node_crypto.timingSafeEqual)(a, b);
7634
- }
7635
- function authenticate(req, res) {
7636
- const header = req.headers["authorization"];
7637
- if (typeof header !== "string" || !header.toLowerCase().startsWith("bearer ")) {
7638
- writeJson(res, 401, {
7639
- error: "Missing or malformed Authorization header. Expected: Bearer <token>."
7640
- });
7641
- return false;
7642
- }
7643
- const presented = header.slice(7).trim();
7644
- if (presented.length === 0) {
7645
- writeJson(res, 401, { error: "Empty Bearer token." });
7646
- return false;
7647
- }
7648
- if (AUTH_TOKENS.length === 0 && IS_DEV) {
7649
- return true;
7650
- }
7651
- const presentedBuf = Buffer.from(presented, "utf8");
7652
- let matched = false;
7653
- for (const token of AUTH_TOKENS) {
7654
- if (safeEqual(presentedBuf, token)) {
7655
- matched = true;
7656
- }
7657
- }
7658
- if (!matched) {
7659
- writeJson(res, 401, { error: "Invalid Bearer token." });
7660
- return false;
7661
- }
7662
- return true;
7663
- }
7664
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
+ }
7665
7749
  const xff = req.headers["x-forwarded-for"];
7666
7750
  if (typeof xff === "string" && xff.length > 0) {
7667
- return xff.split(",")[0].trim();
7751
+ const parts = xff.split(",");
7752
+ return parts[parts.length - 1].trim();
7668
7753
  }
7669
7754
  return req.socket.remoteAddress || "unknown";
7670
7755
  }
@@ -7673,6 +7758,9 @@ function rateLimit(req, res) {
7673
7758
  const now = Date.now();
7674
7759
  const bucket = rateBuckets.get(ip);
7675
7760
  if (!bucket || bucket.resetAt <= now) {
7761
+ if (rateBuckets.size >= RATE_BUCKETS_MAX) {
7762
+ rateBuckets.clear();
7763
+ }
7676
7764
  rateBuckets.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
7677
7765
  return true;
7678
7766
  }
@@ -7796,7 +7884,6 @@ async function main() {
7796
7884
  return;
7797
7885
  }
7798
7886
  if (!rateLimit(req, res)) return;
7799
- if (!authenticate(req, res)) return;
7800
7887
  if (req.url === "/mcp") {
7801
7888
  try {
7802
7889
  const server = createServer();
@@ -7815,6 +7902,19 @@ async function main() {
7815
7902
  return;
7816
7903
  }
7817
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
+ }
7818
7918
  try {
7819
7919
  res.setHeader("X-Accel-Buffering", "no");
7820
7920
  res.setHeader("Cache-Control", "no-cache, no-transform");
@@ -7836,7 +7936,8 @@ async function main() {
7836
7936
  transport,
7837
7937
  res,
7838
7938
  keepAliveTimer,
7839
- createdAt: Date.now()
7939
+ createdAt: Date.now(),
7940
+ ip
7840
7941
  });
7841
7942
  transport.onclose = () => {
7842
7943
  cleanupSession(sessionId);
@@ -7906,20 +8007,23 @@ ${signal} received \u2014 shutting down gracefully...`);
7906
8007
  }
7907
8008
  process.on("SIGTERM", () => shutdown("SIGTERM"));
7908
8009
  process.on("SIGINT", () => shutdown("SIGINT"));
8010
+ httpServer.headersTimeout = 15e3;
8011
+ httpServer.requestTimeout = 6e4;
7909
8012
  httpServer.listen(PORT, () => {
7910
8013
  console.info(`Brainerce MCP server running on http://localhost:${PORT}`);
7911
8014
  console.info(` Streamable HTTP: http://localhost:${PORT}/mcp`);
7912
8015
  console.info(` Legacy SSE: http://localhost:${PORT}/sse`);
7913
8016
  console.info(` Health check: http://localhost:${PORT}/health`);
7914
- console.info(
7915
- ` Auth tokens: ${AUTH_TOKENS.length} configured${AUTH_TOKENS.length === 0 && IS_DEV ? " (dev: any non-empty Bearer accepted)" : ""}`
7916
- );
8017
+ console.info(" Auth: none (public docs server)");
7917
8018
  console.info(
7918
8019
  ` CORS: ${ALLOWED_ORIGINS.length} allow-listed origin(s)${IS_DEV ? " + localhost (dev)" : ""}`
7919
8020
  );
7920
8021
  console.info(
7921
8022
  ` Rate limit: ${RATE_LIMIT_MAX} req / ${Math.round(RATE_LIMIT_WINDOW_MS / 1e3)}s per IP`
7922
8023
  );
8024
+ console.info(
8025
+ ` SSE sessions: max ${MAX_SSE_SESSIONS} total, ${MAX_SSE_SESSIONS_PER_IP} per IP`
8026
+ );
7923
8027
  console.info(` Max body: ${MAX_BODY_BYTES} bytes`);
7924
8028
  });
7925
8029
  }