@binaryandthread/mcp 0.4.7 → 0.4.9

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/README.md CHANGED
@@ -115,4 +115,5 @@ floor. The buy is never blocked on rendering.
115
115
  Some designs are sold in two fits, **regular** and **oversize**, priced apart. `list_products` marks them
116
116
  (`fits`, `price_from`), `get_product` lists sizes and prices per fit (pass `fit` to narrow), and
117
117
  `add_to_cart` **requires** `fit` for them: without it the tool returns an error naming both fits, and
118
- nothing is added. Colours follow the ship-to region (khaki is available on the oversize fit in the US only).
118
+ nothing is added. A design sold in one fit needs no `fit`. Colours and sizes follow the ship-to region:
119
+ `get_product` lists the ones you can buy where the cart ships.
package/dist/bin.js CHANGED
@@ -573,24 +573,116 @@ var BLANK_COLOURS = {
573
573
  "sols-03996": ["black", "white"],
574
574
  "shaka-shmhss": ["black", "white", "khaki"]
575
575
  };
576
+ var STOCK_REGION_BY_COUNTRY = {
577
+ us: "US-CA",
578
+ se: "EU",
579
+ at: "EU",
580
+ be: "EU",
581
+ bg: "EU",
582
+ hr: "EU",
583
+ cy: "EU",
584
+ cz: "EU",
585
+ dk: "EU",
586
+ ee: "EU",
587
+ fi: "EU",
588
+ fr: "EU",
589
+ de: "EU",
590
+ gr: "EU",
591
+ hu: "EU",
592
+ ie: "EU",
593
+ it: "EU",
594
+ lv: "EU",
595
+ lt: "EU",
596
+ lu: "EU",
597
+ mt: "EU",
598
+ nl: "EU",
599
+ pl: "EU",
600
+ pt: "EU",
601
+ ro: "EU",
602
+ sk: "EU",
603
+ si: "EU",
604
+ es: "EU"
605
+ };
606
+ var BLANK_BY_REGION = {
607
+ regular: { "*": "gildan-5000" },
608
+ oversize: { EU: "sols-03996", "US-CA": "shaka-shmhss" }
609
+ };
576
610
  function blankFor(fit, countryCode) {
577
- if (fit === "regular") return "gildan-5000";
578
- return (countryCode ?? "").trim().toLowerCase() === "us" ? "shaka-shmhss" : "sols-03996";
611
+ const region = STOCK_REGION_BY_COUNTRY[(countryCode ?? "").trim().toLowerCase()];
612
+ if (!region) return null;
613
+ const map = BLANK_BY_REGION[fit];
614
+ return map["*"] ?? map[region] ?? null;
579
615
  }
580
616
  var BLANK_SIZES = {
581
617
  "gildan-5000": ["s", "m", "l", "xl", "2xl", "3xl", "4xl", "5xl"],
582
618
  "shaka-shmhss": ["s", "m", "l", "xl", "2xl", "3xl", "4xl", "5xl"],
583
619
  "sols-03996": ["xs", "s", "m", "l", "xl", "2xl", "3xl"]
584
620
  };
621
+ function supplierOf(p) {
622
+ const v = (p.metadata ?? {})["bt_supplier"];
623
+ if (v === void 0 || v === null) return "gelato";
624
+ return v === "gelato" || v === "printful" ? v : null;
625
+ }
626
+ var PRINTFUL_BLANK_COLOURS = {
627
+ "ascolour-5082": ["black", "white"]
628
+ };
629
+ var PRINTFUL_BLANK_SIZES = {
630
+ "ascolour-5082": ["s", "m", "l", "xl", "2xl"]
631
+ };
632
+ var PRINTFUL_SERVED_COUNTRIES = [
633
+ "se",
634
+ "us",
635
+ "at",
636
+ "be",
637
+ "bg",
638
+ "hr",
639
+ "cy",
640
+ "cz",
641
+ "dk",
642
+ "ee",
643
+ "fi",
644
+ "fr",
645
+ "de",
646
+ "gr",
647
+ "hu",
648
+ "ie",
649
+ "it",
650
+ "lv",
651
+ "lt",
652
+ "lu",
653
+ "mt",
654
+ "nl",
655
+ "pl",
656
+ "pt",
657
+ "ro",
658
+ "sk",
659
+ "si",
660
+ "es"
661
+ ];
662
+ function printfulBlankFor(fit, countryCode) {
663
+ if (!PRINTFUL_SERVED_COUNTRIES.includes((countryCode ?? "").trim().toLowerCase())) return null;
664
+ return fit === "regular" ? "ascolour-5082" : null;
665
+ }
585
666
  function sizeKey(size) {
586
667
  const s = size.trim().toLowerCase();
587
668
  return s === "xxl" ? "2xl" : s === "xxxl" ? "3xl" : s;
588
669
  }
589
- function colourBuyable(fit, colour, countryCode) {
590
- return BLANK_COLOURS[blankFor(fit, countryCode)].includes(colour.trim().toLowerCase());
670
+ function blankRule(fit, countryCode, supplier) {
671
+ if (supplier === null) return null;
672
+ if (supplier === "printful") {
673
+ const pb = printfulBlankFor(fit, countryCode);
674
+ return pb ? { colours: PRINTFUL_BLANK_COLOURS[pb], sizes: PRINTFUL_BLANK_SIZES[pb] } : null;
675
+ }
676
+ const blank = blankFor(fit, countryCode);
677
+ return blank ? { colours: BLANK_COLOURS[blank], sizes: BLANK_SIZES[blank] } : null;
591
678
  }
592
- function sizeBuyable(fit, size, countryCode) {
593
- return BLANK_SIZES[blankFor(fit, countryCode)].includes(sizeKey(size));
679
+ function colourBuyable(fit, colour, countryCode, supplier = "gelato") {
680
+ const rule = blankRule(fit, countryCode, supplier);
681
+ return !!rule && rule.colours.includes(colour.trim().toLowerCase());
682
+ }
683
+ function sizeBuyable(fit, size, countryCode, supplier = "gelato") {
684
+ const rule = blankRule(fit, countryCode, supplier);
685
+ return !!rule && rule.sizes.includes(sizeKey(size));
594
686
  }
595
687
  function legacyFit(p) {
596
688
  const md = p.metadata ?? {};
@@ -607,24 +699,26 @@ function variantsOfFit(p, fit) {
607
699
  }
608
700
  function coloursFor(p, fit, countryCode) {
609
701
  const gate = gateFit(p, fit);
702
+ const supplier = supplierOf(p);
610
703
  const out = [];
611
704
  for (const v of variantsOfFit(p, fit)) {
612
705
  const c = variantColour(v);
613
706
  if (!c || out.includes(c)) continue;
614
- if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode)) continue;
707
+ if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode, supplier)) continue;
615
708
  out.push(c);
616
709
  }
617
710
  return out;
618
711
  }
619
712
  function sizesFor(p, fit, colour, countryCode) {
620
713
  const gate = gateFit(p, fit);
714
+ const supplier = supplierOf(p);
621
715
  const target = colour.trim().toLowerCase();
622
716
  const out = [];
623
717
  for (const v of variantsOfFit(p, fit)) {
624
718
  if (variantColour(v) !== target) continue;
625
719
  const s = sizeOfVariant(v);
626
720
  if (!s || out.includes(s)) continue;
627
- if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode)) continue;
721
+ if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode, supplier)) continue;
628
722
  out.push(s);
629
723
  }
630
724
  return out;
@@ -662,21 +756,22 @@ function resolveVariant(p, opts) {
662
756
  }
663
757
  const pool = variantsOfFit(p, fit);
664
758
  const gate = gateFit(p, fit);
759
+ const supplier = supplierOf(p);
665
760
  const colours = coloursFor(p, fit, opts.countryCode);
666
761
  const colour = (opts.colour ?? "").trim().toLowerCase() || (colours[0] ?? null);
667
- if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode)) {
762
+ if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode, supplier)) {
668
763
  return { ok: false, reason: "fit_unavailable", fit: gate, colour };
669
764
  }
670
765
  const size = (opts.size ?? "").trim() || null;
671
766
  const sizeLc = size?.toLowerCase();
672
767
  const buyableSizes = colour ? sizesFor(p, fit, colour, opts.countryCode) : [];
673
768
  const sizeExists = !!size && pool.some((x) => (sizeOfVariant(x) ?? x.title ?? "").toLowerCase() === size.toLowerCase());
674
- if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode)) {
769
+ if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode, supplier)) {
675
770
  return { ok: false, reason: "size_unavailable", fit: gate, size, sizes: buyableSizes };
676
771
  }
677
772
  const sellable = gate && opts.countryCode !== void 0 ? pool.filter((x) => {
678
773
  const s = sizeOfVariant(x);
679
- return !s || sizeBuyable(gate, s, opts.countryCode);
774
+ return !s || sizeBuyable(gate, s, opts.countryCode, supplier);
680
775
  }) : pool;
681
776
  const v = sellable.find((x) => {
682
777
  const colourOk = !colour || variantColour(x) === colour;
@@ -805,25 +900,83 @@ function firstImageUrl(images) {
805
900
  function escapeRegex(s) {
806
901
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
807
902
  }
903
+ function orderStage(o) {
904
+ const status = (o.status ?? "").trim().toLowerCase();
905
+ if (status === "canceled" || status === "refunded") return "reverted";
906
+ switch ((o.fulfillment_status ?? "").trim().toLowerCase()) {
907
+ case "canceled":
908
+ return "reverted";
909
+ case "delivered":
910
+ case "partially_delivered":
911
+ return "delivered";
912
+ case "shipped":
913
+ case "partially_shipped":
914
+ return "shipped";
915
+ case "fulfilled":
916
+ // made, not shipped: to the customer it is simply not shipped yet
917
+ case "partially_fulfilled":
918
+ case "not_fulfilled":
919
+ default:
920
+ return "committed";
921
+ }
922
+ }
923
+ function customerFulfillmentStatus(word) {
924
+ const w = (word ?? "").trim().toLowerCase();
925
+ if (!w) return null;
926
+ return w === "fulfilled" || w === "partially_fulfilled" ? "not_fulfilled" : w;
927
+ }
928
+ async function cartShipTo(cart, regions) {
929
+ const embedded = cart.region?.countries?.length ? cart.region : null;
930
+ const id = cart.region_id ?? cart.region?.id ?? null;
931
+ const region = embedded ?? (id ? (await regions()).find((r) => r.id === id) ?? null : null);
932
+ if (!region) return null;
933
+ const regionCountries = (region.countries ?? []).map((c) => (c.iso_2 ?? "").trim().toLowerCase()).filter(Boolean);
934
+ const address = (cart.shipping_address?.country_code ?? "").trim().toLowerCase() || null;
935
+ const country = address ?? regionCountries[0] ?? null;
936
+ if (!country) return null;
937
+ return { region, country, fromAddress: address !== null, regionCountries };
938
+ }
939
+ function shipToDisagrees(requested, shipTo) {
940
+ const cc = (requested ?? "").trim().toLowerCase();
941
+ if (!cc || cc === shipTo.country) return false;
942
+ return shipTo.fromAddress || !shipTo.regionCountries.includes(cc);
943
+ }
808
944
  function str(v, fallback) {
809
945
  return typeof v === "string" && v.length > 0 ? v : fallback;
810
946
  }
811
947
  function productReadme(p) {
812
948
  return typeof p.description === "string" ? p.description : "";
813
949
  }
950
+ var GELATO_FALLBACK = {
951
+ material: "100% cotton",
952
+ weight: "180 g/m\xB2",
953
+ fit: "Unisex heavyweight crewneck",
954
+ delivery: "Ships in 2\u20135 business days + transit",
955
+ care: "Cold wash, line dry"
956
+ };
957
+ var PRINTFUL_FALLBACK = {
958
+ material: "100% cotton",
959
+ weight: "240 g/m\xB2",
960
+ fit: "Unisex heavyweight crewneck, garment-dyed",
961
+ delivery: "Ships in 2\u20135 business days + transit",
962
+ care: "Machine wash cold, inside out. Do not tumble dry or iron the print. Line dry in shade."
963
+ };
814
964
  function productSpecs(p, fit) {
815
965
  const md = p.metadata ?? {};
816
966
  const byFit = md.specs_by_fit ?? null;
817
967
  const fitKey = fit ?? (byFit && Object.keys(byFit).length === 1 ? Object.keys(byFit)[0] : void 0);
818
968
  const src = fitKey && byFit?.[fitKey] || md;
819
969
  const pick = (k, fallback) => str(src[k] ?? md[k], fallback);
970
+ const d = supplierOf(p) === "printful" ? PRINTFUL_FALLBACK : GELATO_FALLBACK;
820
971
  return [
821
- { key: "material", value: pick("material", "100% cotton") },
822
- { key: "weight", value: pick("weight", "180 g/m\xB2") },
823
- { key: "fit", value: pick("fit", "Unisex heavyweight crewneck") },
972
+ { key: "material", value: pick("material", d.material) },
973
+ { key: "weight", value: pick("weight", d.weight) },
974
+ { key: "fit", value: pick("fit", d.fit) },
824
975
  { key: "print", value: pick("print", "Front print") },
825
- { key: "delivery", value: pick("delivery", "Ships in 2\u20135 business days + transit") },
826
- { key: "care", value: pick("care", "Cold wash, line dry") }
976
+ // Delivery: the product's own line first (Sam 2026-10-01, as the storefront since U1), else the same
977
+ // fallback wording for both suppliers. No per-region times.
978
+ { key: "delivery", value: pick("delivery", d.delivery) },
979
+ { key: "care", value: pick("care", d.care) }
827
980
  ];
828
981
  }
829
982
  function productLore(p) {
@@ -883,7 +1036,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
883
1036
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
884
1037
 
885
1038
  // src/version.ts
886
- var VERSION = "0.4.7";
1039
+ var VERSION = "0.4.9";
887
1040
 
888
1041
  // src/onboarding.ts
889
1042
  var GETTING_STARTED_URI = "bt://getting-started";
@@ -1285,7 +1438,7 @@ function makeTools(client, opts = {}) {
1285
1438
  // the map used to hand out ids for colours the ship-to blank can't print, with the caveat buried
1286
1439
  // in a separate unavailable_colours field. Same derived answer, on the row an agent actually uses.
1287
1440
  // CAT-37 added the SIZE half — the US and EU oversize blanks have different size ranges.
1288
- buyable: (!gFit || !vColour || colourBuyable(gFit, vColour, shipCC)) && (!gFit || !vSize || sizeBuyable(gFit, vSize, shipCC))
1441
+ buyable: (!gFit || !vColour || colourBuyable(gFit, vColour, shipCC, supplierOf(product))) && (!gFit || !vSize || sizeBuyable(gFit, vSize, shipCC, supplierOf(product)))
1289
1442
  };
1290
1443
  });
1291
1444
  const sizeList = colours.length > 0 ? Object.fromEntries(colours.map((c) => [c, sizesForColour(product, c)])) : (product.variants ?? []).map((v) => v.title ?? "?");
@@ -1324,24 +1477,50 @@ function makeTools(client, opts = {}) {
1324
1477
  };
1325
1478
  const add_to_cart = {
1326
1479
  name: "add_to_cart",
1327
- description: "Add a piece to a cart. Pass cart_id to reuse an existing cart (the durable handle), or omit it to start a fresh one. Call get_product first to get valid sizes/colours (or an exact variant_id); for a design sold in two fits (regular / oversize, priced apart) you MUST pass `fit`. qty is capped at 20. Returns the cart_id and the server-side cart summary (VAT included where applicable). `region` (ISO-2) sets the currency for a NEW cart only \u2014 a reused cart keeps its original region.",
1480
+ description: "Add a piece to a cart. Pass cart_id to reuse an existing cart (the durable handle), or omit it to start a fresh one. Call get_product first to get valid sizes/colours (or an exact variant_id); for a design sold in two fits (regular / oversize, priced apart) you MUST pass `fit`. qty is capped at 20. Returns the cart_id and the server-side cart summary (VAT included where applicable). `region` (ISO-2) sets the ship-to country + currency of a NEW cart. A reused cart ships where it already ships: a `region` that disagrees with it is refused (start a new cart instead). If a reused cart has expired or was already checked out, a fresh one is made in the same region and the reply says cart_replaced: true.",
1328
1481
  inputSchema: {
1329
1482
  handle: z.string().optional().describe("product handle (resolves a variant by size + colour; call get_product for valid values)"),
1330
1483
  variant_id: z.string().optional().describe("a specific variant id from get_product (overrides handle/size/colour)"),
1331
1484
  fit: z.string().optional().describe("regular or oversize; REQUIRED when the design comes in both (get_product lists them)"),
1332
- size: z.string().optional().describe("size label from get_product (Regular S / M / L / XL / 2XL; Oversize XS \u2026 3XL)"),
1333
- colour: z.string().optional().describe("piece colour from get_product, e.g. black / white / khaki (khaki: US oversize only; default: the first colour you can buy)"),
1485
+ size: z.string().optional().describe("size label from get_product (it lists the sizes you can buy where the cart ships)"),
1486
+ colour: z.string().optional().describe("piece colour from get_product, e.g. black / white (default: the first colour you can buy where the cart ships)"),
1334
1487
  qty: z.number().int().positive().max(20).optional().describe("quantity, 1\u201320 (default 1)"),
1335
1488
  cart_id: z.string().optional().describe("reuse this cart; omit to create a new one"),
1336
- region: z.string().optional().describe("ISO-2 ship-to country for a NEW cart (e.g. se / us / de); ignored when reusing a cart_id")
1489
+ region: z.string().optional().describe("ISO-2 ship-to country for a NEW cart (e.g. se / us / de); with a cart_id it must match where that cart ships, or nothing is added")
1337
1490
  },
1338
1491
  handler: async (args) => {
1339
- const cc = typeof args.region === "string" && args.region.trim() || regionCC;
1340
- const rr = await client.resolveRegionResult(cc);
1341
- if (rr.requested && !rr.matched) {
1342
- return errorText(`we don't ship to ${sanitize(rr.requested, 8)} yet \u2014 nothing was added. (list_products with a supported region to see prices.)`);
1492
+ const reuse = typeof args.cart_id === "string" && args.cart_id.trim() ? args.cart_id.trim() : void 0;
1493
+ const explicitCC = typeof args.region === "string" && args.region.trim().toLowerCase() || void 0;
1494
+ let existing = null;
1495
+ if (reuse) {
1496
+ try {
1497
+ existing = await client.getCart(reuse);
1498
+ } catch (e) {
1499
+ if (!(e instanceof RecoverableCartError)) throw e;
1500
+ }
1501
+ }
1502
+ let region;
1503
+ let shipCC;
1504
+ if (existing) {
1505
+ const to = await cartShipTo(existing, () => client.getRegions());
1506
+ if (!to) {
1507
+ return errorText(`can't tell where cart ${sanitize(reuse, 60)} ships, so nothing was added. omit cart_id to start a new cart.`);
1508
+ }
1509
+ if (shipToDisagrees(explicitCC, to)) {
1510
+ return errorText(
1511
+ `cart ${sanitize(reuse, 60)} ships to ${sanitize(to.country, 8)}, not ${sanitize(explicitCC, 8)}. nothing was added. omit region to keep adding to this cart, or omit cart_id to start a new cart for ${sanitize(explicitCC, 8)}.`
1512
+ );
1513
+ }
1514
+ region = to.region;
1515
+ shipCC = to.country;
1516
+ } else {
1517
+ const rr = await client.resolveRegionResult(explicitCC || regionCC);
1518
+ if (rr.requested && !rr.matched) {
1519
+ return errorText(`we don't ship to ${sanitize(rr.requested, 8)} yet \u2014 nothing was added. (list_products with a supported region to see prices.)`);
1520
+ }
1521
+ region = rr.region;
1522
+ shipCC = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
1343
1523
  }
1344
- const region = rr.region;
1345
1524
  const qty = typeof args.qty === "number" ? args.qty : 1;
1346
1525
  let variantId = typeof args.variant_id === "string" ? args.variant_id : void 0;
1347
1526
  if (variantId) {
@@ -1355,13 +1534,14 @@ function makeTools(client, opts = {}) {
1355
1534
  const vColour = variantColour(v);
1356
1535
  const vSize = sizeOfVariant(v);
1357
1536
  const gFit = gateFit(owner, variantFit(v));
1358
- const shipCC2 = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
1359
- if (gFit && vColour && !colourBuyable(gFit, vColour, shipCC2)) {
1537
+ const shipCC2 = shipCC;
1538
+ const supplier = supplierOf(owner);
1539
+ if (gFit && vColour && !colourBuyable(gFit, vColour, shipCC2, supplier)) {
1360
1540
  return errorText(
1361
1541
  `${sanitize(vColour, 20)} is not available in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
1362
1542
  );
1363
1543
  }
1364
- if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2)) {
1544
+ if (gFit && vSize && !sizeBuyable(gFit, vSize, shipCC2, supplier)) {
1365
1545
  return errorText(
1366
1546
  `size ${sanitize(vSize, 20)} is not made in ${gFit} for ${sanitize(shipCC2 ?? "this region", 8)}. nothing was added.`
1367
1547
  );
@@ -1377,7 +1557,6 @@ function makeTools(client, opts = {}) {
1377
1557
  return errorText(`no product with handle "${sanitize(handle, 60)}".`);
1378
1558
  }
1379
1559
  const size = typeof args.size === "string" ? args.size : void 0;
1380
- const shipCC = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
1381
1560
  const r = resolveVariant(product, {
1382
1561
  fit: typeof args.fit === "string" ? args.fit : void 0,
1383
1562
  size,
@@ -1414,27 +1593,31 @@ function makeTools(client, opts = {}) {
1414
1593
  }
1415
1594
  variantId = r.variant.id;
1416
1595
  }
1417
- const reuse = typeof args.cart_id === "string" ? args.cart_id : void 0;
1418
1596
  const addTo = async (cartId) => client.addLineItem(cartId, variantId, qty);
1419
1597
  let cart;
1420
- try {
1421
- const targetId = reuse ?? (await client.createCart(region)).id;
1422
- cart = await addTo(targetId);
1423
- } catch (e) {
1424
- if (e instanceof RecoverableCartError && reuse) {
1425
- const fresh = await client.createCart(region);
1426
- cart = await addTo(fresh.id);
1427
- } else {
1428
- throw e;
1598
+ let replaced = !!reuse && !existing;
1599
+ if (existing) {
1600
+ try {
1601
+ cart = await addTo(reuse);
1602
+ } catch (e) {
1603
+ if (!(e instanceof RecoverableCartError)) throw e;
1604
+ replaced = true;
1605
+ cart = await addTo((await client.createCart(region)).id);
1429
1606
  }
1607
+ } else {
1608
+ cart = await addTo((await client.createCart(region)).id);
1430
1609
  }
1431
1610
  const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0);
1611
+ const replacedNote = replaced ? `
1612
+ cart ${sanitize(reuse, 60)} had expired or was already checked out, so this is a NEW cart${existing ? " (same region)" : ""}. use the new cart_id from now on.` : "";
1432
1613
  return result(
1433
1614
  `added.
1434
- cart_id: ${cart.id} (reuse this handle on the next add / checkout)
1615
+ cart_id: ${cart.id} (reuse this handle on the next add / checkout)${replacedNote}
1435
1616
  ${cartSummary(cart, region)}`,
1436
1617
  {
1437
1618
  cart_id: cart.id,
1619
+ // only when a cart_id was passed, so a fresh-cart reply is unchanged
1620
+ ...reuse ? { cart_replaced: replaced, ...replaced ? { replaced_cart_id: reuse } : {} } : {},
1438
1621
  total: cart.total ?? null,
1439
1622
  currency: cart.currency_code ?? region.currency_code ?? null,
1440
1623
  item_count: itemCount,
@@ -1554,7 +1737,7 @@ ${cartSummary(cart, region)}`,
1554
1737
  const out = redactForOutput({ status: res.status, order_id: res.order_id, summary: s ?? null });
1555
1738
  let human;
1556
1739
  if (res.status === "placed") {
1557
- const lines = [`paid \u2014 order ${s?.display_id ? `#${s.display_id}` : "placed"}. order_id: ${res.order_id ?? "\u2014"}`];
1740
+ const lines = [`placed \u2014 order ${s?.display_id ? `#${s.display_id}` : "created"}. order_id: ${res.order_id ?? "\u2014"}`];
1558
1741
  if (s) {
1559
1742
  const cur = s.currency_code;
1560
1743
  const vat = taxSuffix(s.is_tax_inclusive === true);
@@ -1573,9 +1756,15 @@ ${cartSummary(cart, region)}`,
1573
1756
  return { content: [{ type: "text", text: human }], structuredContent: out };
1574
1757
  }
1575
1758
  };
1759
+ const STAGE_WORDS = {
1760
+ committed: "placed, not shipped yet",
1761
+ shipped: "shipped",
1762
+ delivered: "delivered",
1763
+ reverted: "canceled or refunded"
1764
+ };
1576
1765
  const get_order_status = {
1577
1766
  name: "get_order_status",
1578
- description: "Look up an order's status by its display id + the EXACT email on the order. Returns status only (no shipping address). A wrong id/email returns not-found (no enumeration).",
1767
+ description: "Look up an order's status by its display id + the EXACT email on the order. Returns status and stage (placed, shipped or delivered; no shipping address). A wrong id/email returns not-found (no enumeration).",
1579
1768
  inputSchema: {
1580
1769
  display_id: z.string().describe("the order's display id (e.g. 1042)"),
1581
1770
  email: z.string().describe("the exact email on the order")
@@ -1597,17 +1786,20 @@ ${cartSummary(cart, region)}`,
1597
1786
  return errorText(`no order matches that id + email.`);
1598
1787
  }
1599
1788
  const order = looked.order ?? looked;
1600
- const status = sanitize(String(order["status"] ?? order["fulfillment_status"] ?? "unknown"), 60);
1789
+ const rawFulfillment = order["fulfillment_status"];
1790
+ const fulfillment = customerFulfillmentStatus(typeof rawFulfillment === "string" ? rawFulfillment : null);
1791
+ const status = sanitize(String(order["status"] ?? fulfillment ?? "unknown"), 60);
1601
1792
  const display = sanitize(String(order["display_id"] ?? displayId), 30);
1602
- const lines = [`order #${display}`, `status: ${status}`];
1603
- const fulfillment = order["fulfillment_status"];
1604
- if (typeof fulfillment === "string" && fulfillment) {
1793
+ const stage = STAGE_WORDS[orderStage({ status: String(order["status"] ?? ""), fulfillment_status: fulfillment })];
1794
+ const lines = [`order #${display}`, `status: ${status}`, `stage: ${stage}`];
1795
+ if (fulfillment) {
1605
1796
  lines.push(`fulfillment: ${sanitize(fulfillment, 60)}`);
1606
1797
  }
1607
1798
  return result(redactForOutput(lines.join("\n")), {
1608
1799
  display_id: order["display_id"] ?? display,
1609
1800
  status,
1610
- fulfillment_status: typeof fulfillment === "string" ? fulfillment : null
1801
+ stage,
1802
+ fulfillment_status: fulfillment
1611
1803
  });
1612
1804
  }
1613
1805
  };