@binaryandthread/mcp 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/bin.js +197 -40
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Override any of it via `env` for local dev / staging, or to attach your customer
|
|
|
56
56
|
"env": {
|
|
57
57
|
"BT_STORE_URL": "http://localhost:9000", // override the baked-in prod URL (dev/staging)
|
|
58
58
|
"BT_PUBLISHABLE_KEY": "pk_...", // PUBLIC by design, never a secret key
|
|
59
|
-
"BT_REGION": "se", // optional ISO-2
|
|
59
|
+
"BT_REGION": "se", // optional ISO-2 (se · us · any EU country e.g. de/fr/ie); region-resolved, never hardcoded
|
|
60
60
|
"BT_TOKEN": "..." // optional customer JWT (own scope), from your keychain, NEVER the chat
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -71,7 +71,14 @@ Override any of it via `env` for local dev / staging, or to attach your customer
|
|
|
71
71
|
| `BT_STORE_URL` | no | the Medusa Store API base URL (default: production) |
|
|
72
72
|
| `BT_PUBLISHABLE_KEY` | no | the **publishable** key, public by design (default: production; a secret `sk_` key is rejected) |
|
|
73
73
|
| `BT_TOKEN` | no | the customer JWT (own data only); read from the OS keychain / a configured env, **never from the chat** |
|
|
74
|
-
| `BT_REGION` | no | ISO-2 country code to resolve pricing (e.g. `
|
|
74
|
+
| `BT_REGION` | no | ISO-2 country code to resolve pricing — `se` (SEK), `us` (USD), or any EU country e.g. `de`/`fr`/`ie` (EUR); never a hardcoded currency literal. Sets the **default**; `list_products` / `get_product` / `add_to_cart` also take a per-call `region` arg that overrides it (a country we don't ship to is refused, not priced in the wrong currency). |
|
|
75
|
+
|
|
76
|
+
### Onboarding for the agent
|
|
77
|
+
|
|
78
|
+
A cold agent doesn't have to infer the flow: the server exposes a **`getting-started` resource**
|
|
79
|
+
(`bt://getting-started`) describing the `list_products → get_product → add_to_cart → checkout →
|
|
80
|
+
check_payment` flow and the money boundary, and a **`buy` prompt** that walks the purchase (respecting
|
|
81
|
+
that the card + address are entered by the human in the browser, never in the chat).
|
|
75
82
|
|
|
76
83
|
## Security posture (the MCP-specific red-team gates)
|
|
77
84
|
|
package/dist/bin.js
CHANGED
|
@@ -286,24 +286,45 @@ var StoreClient = class {
|
|
|
286
286
|
return this.regionsCache;
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
|
-
* Resolve a region by country code,
|
|
290
|
-
*
|
|
289
|
+
* Resolve a region by country code, WITH provenance (matched / home / requested), so callers on
|
|
290
|
+
* the money path can fail closed on an unmatched explicit country instead of silently pricing in
|
|
291
|
+
* the wrong currency (F-A2). Region #1 is the launch/home default — SEK today, derived, never a
|
|
292
|
+
* hardcoded literal.
|
|
293
|
+
*
|
|
294
|
+
* Semantics:
|
|
295
|
+
* - country given AND a region ships there → { matched: true, home: false }.
|
|
296
|
+
* - country given, none ships there → { matched: false, home: false } (money surfaces MUST
|
|
297
|
+
* refuse; discovery may show the home region + a warning). We still return a region so a
|
|
298
|
+
* discovery command can render *something*, but `matched:false` is the signal to fail closed.
|
|
299
|
+
* - no country given → { matched: false, home: true } (the legitimate
|
|
300
|
+
* launch default — not a "wrong" currency).
|
|
291
301
|
*/
|
|
292
|
-
async
|
|
302
|
+
async resolveRegionResult(countryCode) {
|
|
293
303
|
const regions = this.regionsCache ?? await this.getRegions();
|
|
294
304
|
if (regions.length === 0) {
|
|
295
305
|
throw new Error("no regions configured \u2014 cannot resolve a region for pricing");
|
|
296
306
|
}
|
|
307
|
+
const home = regions[0];
|
|
297
308
|
if (countryCode) {
|
|
298
309
|
const cc = countryCode.toLowerCase();
|
|
299
310
|
const match = regions.find(
|
|
300
311
|
(r) => (r.countries ?? []).some((c) => (c.iso_2 ?? "").toLowerCase() === cc)
|
|
301
312
|
);
|
|
302
313
|
if (match) {
|
|
303
|
-
return match;
|
|
314
|
+
return { region: match, matched: true, requested: cc, home: false };
|
|
304
315
|
}
|
|
316
|
+
this.logWarn("store.region.unmatched", { countryCode: cc, fallback: home.id });
|
|
317
|
+
return { region: home, matched: false, requested: cc, home: false };
|
|
305
318
|
}
|
|
306
|
-
return
|
|
319
|
+
return { region: home, matched: false, requested: void 0, home: true };
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Resolve a region by country code, falling back to region #1. A thin wrapper over
|
|
323
|
+
* resolveRegionResult for callers that only need the region (back-compat).
|
|
324
|
+
* SEK is region #1 by configuration — NEVER a hardcoded literal.
|
|
325
|
+
*/
|
|
326
|
+
async resolveRegion(countryCode) {
|
|
327
|
+
return (await this.resolveRegionResult(countryCode)).region;
|
|
307
328
|
}
|
|
308
329
|
// ----- products (region resolved BEFORE pricing) ---------------------------
|
|
309
330
|
async listProducts(opts) {
|
|
@@ -622,13 +643,53 @@ function productLore(p) {
|
|
|
622
643
|
).map((s) => ({ url: s.url, label: s.label }));
|
|
623
644
|
return { text, sources };
|
|
624
645
|
}
|
|
646
|
+
function isCartTaxInclusive(cart) {
|
|
647
|
+
return (cart?.items ?? []).some((i) => i.is_tax_inclusive === true);
|
|
648
|
+
}
|
|
625
649
|
|
|
626
650
|
// src/server.ts
|
|
627
651
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
628
652
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
653
|
+
|
|
654
|
+
// src/version.ts
|
|
655
|
+
var VERSION = "0.2.0";
|
|
656
|
+
|
|
657
|
+
// src/onboarding.ts
|
|
658
|
+
var GETTING_STARTED_URI = "bt://getting-started";
|
|
659
|
+
var GETTING_STARTED = `binary & thread \u2014 buying from your agent.
|
|
660
|
+
|
|
661
|
+
the flow (each tool returns machine-readable structuredContent, so you can chain without regexing prose):
|
|
662
|
+
1. list_products [region] what's in stock, priced in the ship-to market's currency
|
|
663
|
+
2. get_product <handle> full detail + an authoritative variant_id + sizes + colours
|
|
664
|
+
3. add_to_cart \u2026 start or reuse a cart (returns a durable cart_id handle)
|
|
665
|
+
4. get_cart <cart_id> the authoritative cart \u2014 note: the total is PRE-shipping
|
|
666
|
+
5. checkout <cart_id> mint a pay link + the SHIPPING-INCLUSIVE total the buyer pays
|
|
667
|
+
6. check_payment <session_id> poll once for the result (call again if still pending)
|
|
668
|
+
|
|
669
|
+
the boundary (by design): payment + ship-to details are entered by the human on the storefront.
|
|
670
|
+
they never enter this chat, and you cannot complete a purchase yourself \u2014 you walk them to the door.
|
|
671
|
+
|
|
672
|
+
region + currency: prices and shipping follow the ship-to country. pass region (ISO-2, e.g. se / us /
|
|
673
|
+
de) to list_products / get_product / add_to_cart, or the host can set BT_REGION. a country we don't
|
|
674
|
+
ship to is refused rather than priced in the wrong currency.`;
|
|
675
|
+
function buyPromptText(handle, region) {
|
|
676
|
+
const target = handle ? `"${handle}"` : "the piece the user wants";
|
|
677
|
+
const reg = region ? ` Price it for region "${region}".` : "";
|
|
678
|
+
return `Help me buy ${target} from binary & thread through the MCP tools.${reg}
|
|
679
|
+
|
|
680
|
+
Steps:
|
|
681
|
+
1. If you don't have an exact variant_id, call get_product to get valid sizes/colours + the variant_id.
|
|
682
|
+
2. add_to_cart with the variant_id (or handle + size + colour). Keep the returned cart_id.
|
|
683
|
+
3. Show me the cart total, and remind me it excludes shipping until checkout.
|
|
684
|
+
4. When I confirm, call checkout and give me the pay link + the shipping-inclusive total.
|
|
685
|
+
5. Do NOT ask me for card or address details \u2014 I enter those in the browser. After I pay, use
|
|
686
|
+
check_payment with the session_id to confirm the order.`;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/server.ts
|
|
629
690
|
var SERVER_INFO = {
|
|
630
691
|
name: "binary-and-thread",
|
|
631
|
-
version:
|
|
692
|
+
version: VERSION
|
|
632
693
|
};
|
|
633
694
|
function buildServer(tools) {
|
|
634
695
|
const server = new McpServer(SERVER_INFO);
|
|
@@ -651,7 +712,35 @@ function buildServer(tools) {
|
|
|
651
712
|
);
|
|
652
713
|
toolNames.push(t.name);
|
|
653
714
|
}
|
|
654
|
-
|
|
715
|
+
const registerResource = server.registerResource.bind(server);
|
|
716
|
+
registerResource(
|
|
717
|
+
"getting-started",
|
|
718
|
+
GETTING_STARTED_URI,
|
|
719
|
+
{ title: "binary & thread \u2014 getting started", description: "the buy flow + the money boundary, for a cold agent", mimeType: "text/plain" },
|
|
720
|
+
() => ({ contents: [{ uri: GETTING_STARTED_URI, text: GETTING_STARTED, mimeType: "text/plain" }] })
|
|
721
|
+
);
|
|
722
|
+
const resourceNames = ["getting-started"];
|
|
723
|
+
const registerPrompt = server.registerPrompt.bind(server);
|
|
724
|
+
registerPrompt(
|
|
725
|
+
"buy",
|
|
726
|
+
{ title: "buy a piece", description: "walk through buying a piece (respecting the card-stays-with-the-human boundary)" },
|
|
727
|
+
(args) => ({
|
|
728
|
+
messages: [
|
|
729
|
+
{
|
|
730
|
+
role: "user",
|
|
731
|
+
content: {
|
|
732
|
+
type: "text",
|
|
733
|
+
text: buyPromptText(
|
|
734
|
+
typeof args?.handle === "string" ? args.handle : void 0,
|
|
735
|
+
typeof args?.region === "string" ? args.region : void 0
|
|
736
|
+
)
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
]
|
|
740
|
+
})
|
|
741
|
+
);
|
|
742
|
+
const promptNames = ["buy"];
|
|
743
|
+
return { server, toolNames, resourceNames, promptNames };
|
|
655
744
|
}
|
|
656
745
|
async function startServer(tools) {
|
|
657
746
|
const { server } = buildServer(tools);
|
|
@@ -773,13 +862,25 @@ function fmtPrice(amount, currency) {
|
|
|
773
862
|
const cur = (currency ?? "").toUpperCase();
|
|
774
863
|
return cur ? `${amount} ${cur}` : `${amount}`;
|
|
775
864
|
}
|
|
865
|
+
function taxSuffix(isTaxInclusive) {
|
|
866
|
+
return isTaxInclusive ? " incl. VAT" : "";
|
|
867
|
+
}
|
|
776
868
|
function firstPrice(p) {
|
|
777
869
|
const v = (p.variants ?? [])[0];
|
|
778
870
|
return {
|
|
779
871
|
amount: v?.calculated_price?.calculated_amount ?? null,
|
|
780
|
-
currency: v?.calculated_price?.currency_code ?? null
|
|
872
|
+
currency: v?.calculated_price?.currency_code ?? null,
|
|
873
|
+
taxInclusive: v?.calculated_price?.is_calculated_price_tax_inclusive ?? null
|
|
781
874
|
};
|
|
782
875
|
}
|
|
876
|
+
function cartItems(cart) {
|
|
877
|
+
return (cart.items ?? []).map((li) => ({
|
|
878
|
+
variant_id: li.variant_id ?? null,
|
|
879
|
+
title: sanitize(li.product_title ?? li.title ?? "item", 80),
|
|
880
|
+
qty: li.quantity ?? 1,
|
|
881
|
+
total: li.total ?? null
|
|
882
|
+
}));
|
|
883
|
+
}
|
|
783
884
|
function cartSummary(cart, region) {
|
|
784
885
|
const lines = [];
|
|
785
886
|
lines.push(`cart ${cart.id}`);
|
|
@@ -792,45 +893,56 @@ function cartSummary(cart, region) {
|
|
|
792
893
|
lines.push(` ${li.quantity}x ${title} \u2014 ${fmtPrice(li.total, cart.currency_code ?? region?.currency_code)}`);
|
|
793
894
|
}
|
|
794
895
|
}
|
|
795
|
-
|
|
896
|
+
const shipNote = (cart.shipping_methods ?? []).length > 0 ? "" : " (excl. shipping \u2014 calculated at checkout)";
|
|
897
|
+
lines.push(` total ${fmtPrice(cart.total, cart.currency_code ?? region?.currency_code)}${taxSuffix(isCartTaxInclusive(cart))}${shipNote}`);
|
|
796
898
|
return lines.join("\n");
|
|
797
899
|
}
|
|
798
900
|
function makeTools(client, opts = {}) {
|
|
799
901
|
const regionCC = opts.region;
|
|
800
902
|
const list_products = {
|
|
801
903
|
name: "list_products",
|
|
802
|
-
description: "List the pieces in stock (text-only \u2014 no images, to keep the agent's context lean). Returns each product's handle, price
|
|
803
|
-
inputSchema: {
|
|
904
|
+
description: "List the pieces in stock (text-only \u2014 no images, to keep the agent's context lean). Returns each product's handle, price (VAT included where applicable), and stage. Use get_product for one item's image. Prices are in the region's currency \u2014 pass `region` (an ISO-2 country) to price for a specific market, else the server default.",
|
|
905
|
+
inputSchema: {
|
|
906
|
+
stage: z.string().optional().describe("filter by channel stage: 'main' or 'next'"),
|
|
907
|
+
region: z.string().optional().describe("ISO-2 ship-to country to price for (e.g. se / us / de); overrides the server default")
|
|
908
|
+
},
|
|
804
909
|
handler: async (args) => {
|
|
805
|
-
const
|
|
910
|
+
const cc = typeof args.region === "string" && args.region.trim() || regionCC;
|
|
911
|
+
const rr = await client.resolveRegionResult(cc);
|
|
912
|
+
const region = rr.region;
|
|
806
913
|
const products = await client.listProducts({ region });
|
|
807
914
|
const stage = typeof args.stage === "string" ? args.stage.toLowerCase() : void 0;
|
|
808
915
|
const rows = products.map((p) => ({ p, stage: String(p.metadata?.["channel_stage"] ?? "main") })).filter((r) => stage ? r.stage.toLowerCase() === stage : true);
|
|
809
916
|
if (rows.length === 0) {
|
|
810
|
-
return result("nothing in stock on this branch yet.", { products: [] });
|
|
917
|
+
return result("nothing in stock on this branch yet.", { products: [], currency: region.currency_code, region: rr.requested ?? null });
|
|
811
918
|
}
|
|
812
919
|
const lines = rows.map(({ p, stage: stage2 }) => {
|
|
813
|
-
const { amount, currency } = firstPrice(p);
|
|
920
|
+
const { amount, currency, taxInclusive } = firstPrice(p);
|
|
814
921
|
const handle = sanitize(p.handle ?? p.id, 60);
|
|
815
922
|
const title = sanitize(p.title ?? "", 80);
|
|
816
|
-
return `${handle} ${fmtPrice(amount, currency)}
|
|
923
|
+
return `${handle} ${fmtPrice(amount, currency)}${taxSuffix(taxInclusive)} [${stage2}] ${title}`.trimEnd();
|
|
817
924
|
});
|
|
818
925
|
const productList = rows.map(({ p, stage: stage2 }) => {
|
|
819
|
-
const { amount, currency } = firstPrice(p);
|
|
820
|
-
return { handle: p.handle ?? p.id, title: p.title ?? "", price: amount, currency, stage: stage2 };
|
|
926
|
+
const { amount, currency, taxInclusive } = firstPrice(p);
|
|
927
|
+
return { handle: p.handle ?? p.id, title: sanitize(p.title ?? "", 80), price: amount, currency, tax_inclusive: taxInclusive, stage: stage2 };
|
|
821
928
|
});
|
|
822
|
-
return result(lines.join("\n"), { products: productList });
|
|
929
|
+
return result(lines.join("\n"), { products: productList, currency: region.currency_code, region: rr.requested ?? null });
|
|
823
930
|
}
|
|
824
931
|
};
|
|
825
932
|
const get_product = {
|
|
826
933
|
name: "get_product",
|
|
827
934
|
// M3 ★ — a hidden line for the schema-readers (devs read tool descriptions). Rewards the
|
|
828
935
|
// contributor instinct + hints at a real feature (the next branch / drops). Never alters behaviour.
|
|
829
|
-
description: "Get one piece in full: its readme, the spec sheet (material/weight/fit/print/delivery/care), sizes + price
|
|
830
|
-
inputSchema: {
|
|
936
|
+
description: "Get one piece in full: its readme, the spec sheet (material/weight/fit/print/delivery/care), sizes + price (VAT included where applicable), the lore (the story behind it, with sources), and its image (rendered + the Blob URL as a text floor). Prices are in the region's currency \u2014 pass `region` (ISO-2) to price for a market. // you read the schema. that's the contributor instinct. ask me about the next branch.",
|
|
937
|
+
inputSchema: {
|
|
938
|
+
handle: z.string().describe("the product handle/slug (from list_products)"),
|
|
939
|
+
region: z.string().optional().describe("ISO-2 ship-to country to price for (e.g. se / us / de); overrides the server default")
|
|
940
|
+
},
|
|
831
941
|
handler: async (args) => {
|
|
832
942
|
const handle = String(args.handle ?? "");
|
|
833
|
-
const
|
|
943
|
+
const cc = typeof args.region === "string" && args.region.trim() || regionCC;
|
|
944
|
+
const rr = await client.resolveRegionResult(cc);
|
|
945
|
+
const region = rr.region;
|
|
834
946
|
const product = await client.getProduct(handle, { region });
|
|
835
947
|
if (!product) {
|
|
836
948
|
return errorText(`no product with handle "${sanitize(handle, 60)}". try list_products.`);
|
|
@@ -857,7 +969,7 @@ function makeTools(client, opts = {}) {
|
|
|
857
969
|
}
|
|
858
970
|
const v0 = (product.variants ?? [])[0];
|
|
859
971
|
if (v0?.calculated_price) {
|
|
860
|
-
lines.push(`price: ${fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code)}
|
|
972
|
+
lines.push(`price: ${fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code)}${taxSuffix(v0.calculated_price.is_calculated_price_tax_inclusive)}`);
|
|
861
973
|
}
|
|
862
974
|
const specs = productSpecs(product);
|
|
863
975
|
lines.push("specs:");
|
|
@@ -876,36 +988,59 @@ function makeTools(client, opts = {}) {
|
|
|
876
988
|
const img = await buildImageContent(product.thumbnail);
|
|
877
989
|
content.push(...img);
|
|
878
990
|
}
|
|
991
|
+
const variantMap = (product.variants ?? []).map((v) => ({
|
|
992
|
+
variant_id: v.id,
|
|
993
|
+
size: v.title ?? null,
|
|
994
|
+
colour: variantColour(v) ?? null,
|
|
995
|
+
price: v.calculated_price?.calculated_amount ?? null
|
|
996
|
+
}));
|
|
997
|
+
const sizeList = colours.length > 0 ? Object.fromEntries(colours.map((c) => [c, sizesForColour(product, c)])) : (product.variants ?? []).map((v) => v.title ?? "?");
|
|
879
998
|
return {
|
|
880
999
|
content,
|
|
1000
|
+
// G8/D3 — the untrusted backend TEXT (title/readme/specs/lore) is sanitize()'d before it
|
|
1001
|
+
// enters the machine-readable path too (the header's promise), not just the text block;
|
|
1002
|
+
// ids/urls/numbers pass through redactForOutput unchanged.
|
|
881
1003
|
structuredContent: redactForOutput({
|
|
882
1004
|
handle: product.handle ?? handle,
|
|
883
|
-
title: product.title ?? "",
|
|
1005
|
+
title: sanitize(product.title ?? "", 120),
|
|
884
1006
|
price: v0?.calculated_price?.calculated_amount ?? null,
|
|
885
1007
|
currency: v0?.calculated_price?.currency_code ?? null,
|
|
1008
|
+
tax_inclusive: v0?.calculated_price?.is_calculated_price_tax_inclusive ?? null,
|
|
1009
|
+
region: rr.requested ?? null,
|
|
886
1010
|
colours,
|
|
1011
|
+
sizes: sizeList,
|
|
1012
|
+
variants: variantMap,
|
|
887
1013
|
image_url: product.thumbnail ?? null,
|
|
888
1014
|
// the PDP content, machine-readable: readme + the spec sheet + the lore (+ sources).
|
|
889
|
-
readme: productReadme(product),
|
|
890
|
-
specs,
|
|
891
|
-
lore
|
|
1015
|
+
readme: sanitize(productReadme(product), 2e3),
|
|
1016
|
+
specs: specs.map((s) => ({ key: sanitize(s.key, 40), value: sanitize(s.value, 200) })),
|
|
1017
|
+
lore: {
|
|
1018
|
+
text: sanitize(lore.text ?? "", 1200),
|
|
1019
|
+
sources: lore.sources.map((s) => ({ label: sanitize(s.label, 120), url: s.url }))
|
|
1020
|
+
}
|
|
892
1021
|
})
|
|
893
1022
|
};
|
|
894
1023
|
}
|
|
895
1024
|
};
|
|
896
1025
|
const add_to_cart = {
|
|
897
1026
|
name: "add_to_cart",
|
|
898
|
-
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. Returns the cart_id and the server-side cart summary (
|
|
1027
|
+
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). 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.",
|
|
899
1028
|
inputSchema: {
|
|
900
|
-
handle: z.string().optional().describe("product handle (resolves a variant by size + colour)"),
|
|
901
|
-
variant_id: z.string().optional().describe("a specific variant id (overrides handle/size/colour)"),
|
|
902
|
-
size: z.string().optional().describe("size label, e.g. M / L / XL"),
|
|
903
|
-
colour: z.string().optional().describe("piece colour, e.g. white / black (default: the product's first colour)"),
|
|
904
|
-
qty: z.number().int().positive().max(20).optional().describe("quantity (default 1)"),
|
|
905
|
-
cart_id: z.string().optional().describe("reuse this cart; omit to create a new one")
|
|
1029
|
+
handle: z.string().optional().describe("product handle (resolves a variant by size + colour; call get_product for valid values)"),
|
|
1030
|
+
variant_id: z.string().optional().describe("a specific variant id from get_product (overrides handle/size/colour)"),
|
|
1031
|
+
size: z.string().optional().describe("size label from get_product, e.g. S / M / L / XL / 2XL"),
|
|
1032
|
+
colour: z.string().optional().describe("piece colour from get_product, e.g. white / black (default: the product's first colour)"),
|
|
1033
|
+
qty: z.number().int().positive().max(20).optional().describe("quantity, 1\u201320 (default 1)"),
|
|
1034
|
+
cart_id: z.string().optional().describe("reuse this cart; omit to create a new one"),
|
|
1035
|
+
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")
|
|
906
1036
|
},
|
|
907
1037
|
handler: async (args) => {
|
|
908
|
-
const
|
|
1038
|
+
const cc = typeof args.region === "string" && args.region.trim() || regionCC;
|
|
1039
|
+
const rr = await client.resolveRegionResult(cc);
|
|
1040
|
+
if (rr.requested && !rr.matched) {
|
|
1041
|
+
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.)`);
|
|
1042
|
+
}
|
|
1043
|
+
const region = rr.region;
|
|
909
1044
|
const qty = typeof args.qty === "number" ? args.qty : 1;
|
|
910
1045
|
let variantId = typeof args.variant_id === "string" ? args.variant_id : void 0;
|
|
911
1046
|
if (!variantId) {
|
|
@@ -952,14 +1087,17 @@ ${cartSummary(cart, region)}`,
|
|
|
952
1087
|
cart_id: cart.id,
|
|
953
1088
|
total: cart.total ?? null,
|
|
954
1089
|
currency: cart.currency_code ?? region.currency_code ?? null,
|
|
955
|
-
item_count: itemCount
|
|
1090
|
+
item_count: itemCount,
|
|
1091
|
+
items: cartItems(cart),
|
|
1092
|
+
// A2 — `total` is pre-shipping until checkout attaches a method; flag it for the agent.
|
|
1093
|
+
total_excludes_shipping: (cart.shipping_methods ?? []).length === 0
|
|
956
1094
|
}
|
|
957
1095
|
);
|
|
958
1096
|
}
|
|
959
1097
|
};
|
|
960
1098
|
const get_cart = {
|
|
961
1099
|
name: "get_cart",
|
|
962
|
-
description: "Show a cart's current contents and total (
|
|
1100
|
+
description: "Show a cart's current contents and total (VAT included where applicable), straight from the server.",
|
|
963
1101
|
inputSchema: { cart_id: z.string().describe("the cart handle from add_to_cart") },
|
|
964
1102
|
handler: async (args) => {
|
|
965
1103
|
const cartId = String(args.cart_id ?? "");
|
|
@@ -971,7 +1109,10 @@ ${cartSummary(cart, region)}`,
|
|
|
971
1109
|
cart_id: cart.id,
|
|
972
1110
|
total: cart.total ?? null,
|
|
973
1111
|
currency: cart.currency_code ?? region.currency_code ?? null,
|
|
974
|
-
item_count: itemCount
|
|
1112
|
+
item_count: itemCount,
|
|
1113
|
+
items: cartItems(cart),
|
|
1114
|
+
// A2 — signal that `total` is pre-shipping so a chaining agent doesn't quote it as final.
|
|
1115
|
+
total_excludes_shipping: (cart.shipping_methods ?? []).length === 0
|
|
975
1116
|
});
|
|
976
1117
|
} catch (e) {
|
|
977
1118
|
if (e instanceof RecoverableCartError) {
|
|
@@ -991,8 +1132,9 @@ ${cartSummary(cart, region)}`,
|
|
|
991
1132
|
handler: async (args) => {
|
|
992
1133
|
const cartId = String(args.cart_id ?? "");
|
|
993
1134
|
const email = typeof args.email === "string" ? args.email : void 0;
|
|
1135
|
+
let shipped;
|
|
994
1136
|
try {
|
|
995
|
-
await client.ensureCartShippingMethod(cartId);
|
|
1137
|
+
shipped = await client.ensureCartShippingMethod(cartId);
|
|
996
1138
|
} catch (e) {
|
|
997
1139
|
if (e instanceof RecoverableCartError) {
|
|
998
1140
|
return errorText("that cart is no longer valid (expired or already committed). add an item to start a fresh one.");
|
|
@@ -1000,9 +1142,11 @@ ${cartSummary(cart, region)}`,
|
|
|
1000
1142
|
return errorText("couldn't attach a shipping method to this cart \u2014 checkout isn't available for it right now.");
|
|
1001
1143
|
}
|
|
1002
1144
|
const { checkout_url, session_id } = await client.mintCheckout(cartId, email, "mcp");
|
|
1145
|
+
const totalStr = `${fmtPrice(shipped.total, shipped.currency_code)}${taxSuffix(isCartTaxInclusive(shipped))}`;
|
|
1003
1146
|
const body = [
|
|
1004
1147
|
"checkout ready. open this link in your browser to pay:",
|
|
1005
1148
|
` ${checkout_url}`,
|
|
1149
|
+
` total (incl. shipping): ${totalStr}`,
|
|
1006
1150
|
"",
|
|
1007
1151
|
"payment details are entered on the storefront (Stripe) \u2014 they never enter this chat, and",
|
|
1008
1152
|
"I can't complete this for you. once you've paid, call check_payment with:",
|
|
@@ -1012,7 +1156,12 @@ ${cartSummary(cart, region)}`,
|
|
|
1012
1156
|
// one dry true line: an agent can do everything up to the card. The card is the human's.
|
|
1013
1157
|
"I staged it, priced it, and walked you to the door. the card swipe is yours \u2014 by design."
|
|
1014
1158
|
].join("\n");
|
|
1015
|
-
return result(redactForOutput(body), {
|
|
1159
|
+
return result(redactForOutput(body), {
|
|
1160
|
+
checkout_url,
|
|
1161
|
+
session_id,
|
|
1162
|
+
total: shipped.total ?? null,
|
|
1163
|
+
currency: shipped.currency_code ?? null
|
|
1164
|
+
});
|
|
1016
1165
|
}
|
|
1017
1166
|
};
|
|
1018
1167
|
const check_payment = {
|
|
@@ -1021,7 +1170,15 @@ ${cartSummary(cart, region)}`,
|
|
|
1021
1170
|
inputSchema: { session_id: z.string().describe("the session_id from checkout") },
|
|
1022
1171
|
handler: async (args) => {
|
|
1023
1172
|
const sessionId = String(args.session_id ?? "");
|
|
1024
|
-
|
|
1173
|
+
let res;
|
|
1174
|
+
try {
|
|
1175
|
+
res = await client.poll(sessionId);
|
|
1176
|
+
} catch (e) {
|
|
1177
|
+
if (e instanceof StoreApiError && e.status === 404) {
|
|
1178
|
+
return errorText("that checkout session is unknown or expired \u2014 mint a fresh one with checkout.");
|
|
1179
|
+
}
|
|
1180
|
+
throw e;
|
|
1181
|
+
}
|
|
1025
1182
|
const out = redactForOutput({ status: res.status, order_id: res.order_id });
|
|
1026
1183
|
const human = res.status === "placed" ? `paid \u2014 order placed. order_id: ${out.order_id ?? "\u2014"}` : `status: ${out.status} (call check_payment again in a moment if still pending)`;
|
|
1027
1184
|
return { content: [{ type: "text", text: human }], structuredContent: out };
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../store-client/src/redact.ts","../../store-client/src/client.ts","../../store-client/src/defaults.ts","../../store-client/src/host-allowlist.ts","../../store-client/src/colour.ts","../../store-client/src/product.ts","../src/server.ts","../src/tools.ts","../src/sanitize.ts","../src/images.ts","../src/eggs.ts","../src/config.ts","../src/bin.ts"],"sourcesContent":["// Secret/PII redaction for logs and error envelopes (SECURITY G10).\n//\n// The single rule for the whole channel: Authorization headers, tokens, and full\n// emails NEVER reach a log line, an error message, or an MCP/CLI transcript. This\n// function is applied by the StoreClient's logger and error mapper before anything\n// is emitted. It is consumed by BOTH the CLI and (Wave 4) the MCP — one redactor.\n\nconst SENSITIVE_KEYS = new Set([\n \"authorization\",\n \"token\",\n \"access_token\",\n \"refresh_token\",\n \"jwt\",\n \"idempotency-key\",\n \"x-publishable-api-key\",\n \"password\",\n \"secret\",\n \"cookie\",\n \"set-cookie\",\n])\n\nconst MASK = \"[REDACTED]\"\n\n// Bearer-token-in-a-string (e.g. \"Authorization: Bearer abc.def.ghi\" or a stray \"Bearer xyz\").\nconst BEARER_RE = /Bearer\\s+[A-Za-z0-9._\\-]+/gi\n// A plausible email anywhere in a string.\nconst EMAIL_RE = /([^\\s@]+)@([^\\s@]+\\.[^\\s@]+)/g\n\n/** Mask an email to `d***@x` form (first char + stars, host first char). */\nexport function maskEmail(email: string): string {\n const at = email.indexOf(\"@\")\n if (at <= 0) {\n return email\n }\n const local = email.slice(0, at)\n const host = email.slice(at + 1)\n const localMasked = local[0] + \"*\".repeat(Math.max(2, local.length - 1))\n const hostMasked = host.length > 0 ? host[0] : \"x\"\n return `${localMasked}@${hostMasked}`\n}\n\nfunction redactString(s: string): string {\n return s.replace(BEARER_RE, \"Bearer [REDACTED]\").replace(EMAIL_RE, (_m, _l, _h) =>\n maskEmail(`${_l}@${_h}`)\n )\n}\n\n/**\n * Deep-redact an object/array/string. Returns a structurally-similar value with\n * sensitive keys masked, Bearer tokens stripped, and emails masked. Never throws.\n *\n * `_seen` memoises input-object → its REDACTED output. This does double duty: it makes the\n * function cycle-safe (a revisited object resolves to its own — possibly still-filling — output\n * container) AND ensures a REPEATED reference is redacted every time. The previous WeakSet guard\n * returned the *original* object on revisit, leaking any token/email reachable via a 2nd path (G10).\n * The output container is registered BEFORE recursing so a cycle points at the redacted copy.\n */\nexport function redact<T>(input: T, _seen?: Map<object, unknown>): T {\n if (input == null) {\n return input\n }\n if (typeof input === \"string\") {\n return redactString(input) as unknown as T\n }\n if (typeof input !== \"object\") {\n return input\n }\n\n const seen = _seen ?? new Map<object, unknown>()\n const existing = seen.get(input as object)\n if (existing !== undefined) {\n return existing as T\n }\n\n if (Array.isArray(input)) {\n const out: unknown[] = []\n seen.set(input as object, out) // register before recursing (cycle-safe)\n for (const v of input) {\n out.push(redact(v, seen))\n }\n return out as unknown as T\n }\n\n const out: Record<string, unknown> = {}\n seen.set(input as object, out) // register before recursing (cycle-safe)\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key.toLowerCase())) {\n out[key] = MASK\n } else if (key.toLowerCase() === \"email\" && typeof value === \"string\") {\n out[key] = maskEmail(value)\n } else {\n out[key] = redact(value, seen)\n }\n }\n return out as unknown as T\n}\n","// StoreClient — the ONE shared HTTP client over the B&T Medusa Store API.\n//\n// Design (ROBUSTNESS §0): the clients are DUMB. Every dangerous property lives in the\n// backend. This module is the single place the channel's HTTP-client invariants live so\n// they are written once and contract-tested once (the CLI and the Wave-4 MCP both use it):\n//\n// - publishable key on EVERY request (public by design, T6); customer JWT when present\n// - Idempotency-Key (crypto.randomUUID) on EVERY mutation, STABLE across a retry (§2)\n// - retry 5xx + network errors with jittered backoff; NEVER retry a 4xx (F-A1/F-I2)\n// - region resolved before pricing — never a bare currency literal (F-A2/INV-11)\n// - 404/409/cart.completed on a cart mutation → RecoverableCartError (CLI makes a fresh cart) (F-B2)\n// - all logs + error envelopes pass through redact() — no token/email ever leaks (G10)\n// - NO secret key is ever stored or sent — publishable key only (T6)\n\nimport { redact } from \"./redact\"\nimport type {\n CartLike,\n ClientLogger,\n DropRow,\n HeadlessCheckout,\n OrderSummary,\n PollResult,\n ProductLike,\n RegionLike,\n ReportResult,\n RequestLinkResult,\n RetryPolicy,\n ShippingOptionLike,\n StoreClientOptions,\n VerifyLinkResult,\n WhoAmI,\n} from \"./types\"\n\nconst DEFAULT_RETRY: RetryPolicy = {\n attempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 2000,\n}\n\n/** Cap on how long we'll honor a server's Retry-After, so a hostile/huge value can't hang us. */\nconst RETRY_AFTER_MAX_MS = 10_000\n\n/**\n * Per-attempt request timeout. A black-holed connection (socket accepted, no response) would\n * otherwise never settle, hanging every command/tool AND defeating the checkout poll's 20-min\n * deadline (F-I3) — the deadline can't be re-checked while stuck awaiting a dead fetch. 15s is\n * generous for the Store API while still failing closed on a hang.\n */\nconst DEFAULT_TIMEOUT_MS = 15_000\n\n/**\n * Extra fields to request on product reads. `+metadata` ADDS metadata to Medusa's default field set\n * (it doesn't replace it — variants + calculated_price still come through). metadata is where lore /\n * lore_sources / per-product spec fields live, and the Store API omits it unless asked. The `+` is\n * URL-encoded to %2B by URLSearchParams, which Medusa decodes back to `+metadata`.\n */\nconst PRODUCT_FIELDS = \"+metadata\"\n\n/**\n * Parse an HTTP Retry-After header → ms, or null if absent/unparseable. Accepts both forms:\n * delta-seconds (\"2\") and an HTTP-date. Capped at RETRY_AFTER_MAX_MS (d12).\n */\nfunction parseRetryAfterMs(res: Response): number | null {\n let raw: string | null = null\n try {\n raw = res.headers?.get?.(\"retry-after\") ?? null\n } catch {\n return null\n }\n if (!raw) {\n return null\n }\n const secs = Number(raw)\n let ms: number\n if (Number.isFinite(secs)) {\n ms = secs * 1000\n } else {\n const dateMs = Date.parse(raw)\n if (!Number.isFinite(dateMs)) {\n return null\n }\n ms = dateMs - Date.now()\n }\n return Math.min(Math.max(0, ms), RETRY_AFTER_MAX_MS)\n}\n\n/** A cart error the CLI maps to \"discard local cart_id, create a fresh cart, re-add\" (F-B2). */\nexport class RecoverableCartError extends Error {\n readonly recoverable = true as const\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = \"RecoverableCartError\"\n this.status = status\n }\n}\n\n/** A non-recoverable API error. Its message/envelope is already redacted (G10). */\nexport class StoreApiError extends Error {\n readonly status: number\n readonly path: string\n constructor(message: string, status: number, path: string) {\n super(message)\n this.name = \"StoreApiError\"\n this.status = status\n this.path = path\n }\n}\n\ntype HttpMethod = \"GET\" | \"POST\" | \"DELETE\"\n\ninterface RequestOpts {\n body?: unknown\n /** Mutations carry an Idempotency-Key. */\n idempotent?: boolean\n /** Treat a 404/409/cart.completed as a RecoverableCartError (cart mutations). */\n cartRecovery?: boolean\n query?: Record<string, string | number | undefined>\n /**\n * Statuses for which a 4xx is EXPECTED, not an error — the client_error envelope is NOT\n * logged (BUG 3: a guest whoami 401 is the normal unauthenticated path, not noise to dump).\n * The error is still thrown so the caller can branch on it.\n */\n expected4xx?: number[]\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/** crypto.randomUUID with a defensive fallback (older runtimes). */\nfunction newUuid(): string {\n const g = globalThis as unknown as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) {\n return g.crypto.randomUUID()\n }\n // RFC4122-ish fallback — never the happy path, just defensive.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === \"x\" ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nexport class StoreClient {\n private readonly baseUrl: string\n private readonly publishableKey: string\n private readonly jwt?: string\n private readonly fetchImpl: typeof fetch\n private readonly logger: ClientLogger\n private readonly retry: RetryPolicy\n private readonly sleepImpl: (ms: number) => Promise<void>\n private readonly timeoutMs: number\n\n /** Cached resolved regions (read-only catalog optimisation). */\n private regionsCache: RegionLike[] | null = null\n\n constructor(opts: StoreClientOptions) {\n if (!opts.baseUrl) {\n throw new Error(\"StoreClient: baseUrl is required\")\n }\n if (!opts.publishableKey) {\n throw new Error(\"StoreClient: publishableKey is required\")\n }\n // Guard against accidentally shipping a secret key (T6 — defensive).\n if (/^sk_/.test(opts.publishableKey)) {\n throw new Error(\"StoreClient: refusing a secret key — the client carries only the publishable key (T6)\")\n }\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\")\n this.publishableKey = opts.publishableKey\n this.jwt = opts.jwt\n this.fetchImpl = opts.fetchImpl ?? (globalThis.fetch as typeof fetch)\n this.logger = opts.logger ?? console\n this.retry = { ...DEFAULT_RETRY, ...(opts.retry ?? {}) }\n this.sleepImpl = opts.sleepImpl ?? sleep\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n }\n\n // ----- the request engine --------------------------------------------------\n\n private buildUrl(path: string, query?: RequestOpts[\"query\"]): string {\n const url = new URL(this.baseUrl + path)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined && v !== null && v !== \"\") {\n url.searchParams.set(k, String(v))\n }\n }\n }\n return url.toString()\n }\n\n private baseHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"x-publishable-api-key\": this.publishableKey,\n }\n if (this.jwt) {\n headers[\"Authorization\"] = `Bearer ${this.jwt}`\n }\n return headers\n }\n\n private backoffMs(attemptIndex: number): number {\n // exponential base with full jitter, capped.\n const exp = this.retry.baseDelayMs * 2 ** attemptIndex\n const capped = Math.min(exp, this.retry.maxDelayMs)\n return Math.floor(Math.random() * (capped + 1))\n }\n\n private async request<T>(method: HttpMethod, path: string, opts: RequestOpts = {}): Promise<T> {\n const headers = this.baseHeaders()\n // Generate the idempotency key ONCE — it is reused across retries so a retried\n // mutation can never spawn a duplicate (ROBUSTNESS §2).\n if (opts.idempotent) {\n headers[\"Idempotency-Key\"] = newUuid()\n }\n const url = this.buildUrl(path, opts.query)\n const init: RequestInit = {\n method,\n headers,\n ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),\n }\n\n let lastErr: unknown\n for (let attempt = 0; attempt < this.retry.attempts; attempt++) {\n // A server-dictated delay for THIS attempt's backoff (429 Retry-After); null → jittered backoff.\n let retryAfterMs: number | null = null\n // Bound THIS attempt: a hung connection is aborted after timeoutMs. The AbortError lands in\n // the catch below and is treated as a retryable network error (so the backoff + retry, and\n // ultimately the exhausted-retries StoreApiError, all apply). Fresh controller per attempt.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n try {\n const res = await this.fetchImpl(url as unknown as string, { ...init, signal: controller.signal })\n if (res.ok) {\n // 204 / empty body tolerance\n const text = await res.text()\n return (text ? JSON.parse(text) : {}) as T\n }\n\n // Read the error body defensively (may not be JSON).\n let parsed: Record<string, unknown> = {}\n try {\n const t = await res.text()\n parsed = t ? (JSON.parse(t) as Record<string, unknown>) : {}\n } catch {\n parsed = {}\n }\n\n // Cart recovery: a stale/completed cart on a cart mutation (F-B2).\n if (\n opts.cartRecovery &&\n (res.status === 404 ||\n res.status === 409 ||\n String(parsed.type ?? \"\").includes(\"cart.completed\") ||\n /cart.*complete/i.test(String(parsed.message ?? \"\")))\n ) {\n throw new RecoverableCartError(\n `cart is stale or completed (HTTP ${res.status})`,\n res.status\n )\n }\n\n // 429 Too Many Requests — RETRYABLE, honoring Retry-After (d12). Must be handled BEFORE\n // the generic 4xx-no-retry rule below, or a rate limit becomes a hard failure.\n if (res.status === 429) {\n retryAfterMs = parseRetryAfterMs(res)\n lastErr = new StoreApiError(`rate limited (HTTP 429)`, 429, path)\n this.logWarn(\"store.request.rate_limited\", {\n path,\n attempt: attempt + 1,\n retryAfterMs,\n })\n } else if (res.status >= 400 && res.status < 500) {\n // other 4xx is a client error — do NOT retry.\n // Suppress the envelope for EXPECTED 4xx (e.g. a guest whoami 401 — BUG 3): it's a\n // normal branch the caller handles, not noise to dump to the user's terminal.\n if (!(opts.expected4xx ?? []).includes(res.status)) {\n this.logError(\"store.request.client_error\", { path, status: res.status, body: parsed })\n }\n throw new StoreApiError(\n `request failed (HTTP ${res.status})`,\n res.status,\n path\n )\n } else {\n // 5xx — retryable. Fall through to the retry loop.\n lastErr = new StoreApiError(`upstream error (HTTP ${res.status})`, res.status, path)\n this.logWarn(\"store.request.retryable\", { path, status: res.status, attempt: attempt + 1 })\n }\n } catch (e) {\n // RecoverableCartError / StoreApiError(4xx) must propagate immediately.\n if (e instanceof RecoverableCartError) {\n throw e\n }\n if (e instanceof StoreApiError && e.status >= 400 && e.status < 500) {\n throw e\n }\n // Network error, an aborted (timed-out) request, or a 5xx StoreApiError → retryable.\n lastErr = e\n this.logWarn(\"store.request.network_retry\", { path, attempt: attempt + 1 })\n } finally {\n // Always clear the per-attempt timeout — on success-return, on a rethrow, or before a retry.\n clearTimeout(timer)\n }\n\n // Backoff before the next attempt (skip after the final attempt). A 429 Retry-After, when\n // present, dictates the delay; otherwise jittered exponential backoff (d12).\n if (attempt < this.retry.attempts - 1) {\n await this.sleepImpl(retryAfterMs ?? this.backoffMs(attempt))\n }\n }\n\n // Exhausted retries. The thrown envelope is already free of secrets (we only ever\n // log/throw redacted, derived data — never the request headers/body).\n this.logError(\"store.request.exhausted\", { path, attempts: this.retry.attempts })\n if (lastErr instanceof StoreApiError) {\n throw lastErr\n }\n throw new StoreApiError(\n `request failed after ${this.retry.attempts} attempts: ${path}`,\n 0,\n path\n )\n }\n\n private logWarn(event: string, ctx: Record<string, unknown>): void {\n this.logger.warn(event, redact(ctx))\n }\n\n private logError(event: string, ctx: Record<string, unknown>): void {\n this.logger.error(event, redact(ctx))\n }\n\n // ----- regions (F-A2 / INV-11) ---------------------------------------------\n\n async getRegions(): Promise<RegionLike[]> {\n const data = await this.request<{ regions: RegionLike[] }>(\"GET\", \"/store/regions\")\n this.regionsCache = data.regions ?? []\n return this.regionsCache\n }\n\n /**\n * Resolve a region by country code, falling back to region #1.\n * SEK is region #1 by configuration — NEVER a hardcoded literal.\n */\n async resolveRegion(countryCode?: string): Promise<RegionLike> {\n const regions = this.regionsCache ?? (await this.getRegions())\n if (regions.length === 0) {\n // Fail closed: refuse to price without a resolved region (F-A2).\n throw new Error(\"no regions configured — cannot resolve a region for pricing\")\n }\n if (countryCode) {\n const cc = countryCode.toLowerCase()\n const match = regions.find((r) =>\n (r.countries ?? []).some((c) => (c.iso_2 ?? \"\").toLowerCase() === cc)\n )\n if (match) {\n return match\n }\n }\n // Region #1 — the launch region (SEK today, derived not hardcoded).\n return regions[0]!\n }\n\n // ----- products (region resolved BEFORE pricing) ---------------------------\n\n async listProducts(opts?: { region?: RegionLike; countryCode?: string }): Promise<ProductLike[]> {\n const region = opts?.region ?? (await this.resolveRegion(opts?.countryCode))\n // Paginate (L12): a single limit:100 call silently drops the 101st product. Page through until a\n // short page, with a hard cap so a misbehaving API can never spin this unbounded.\n const PAGE = 100\n const HARD_CAP = 1000\n const all: ProductLike[] = []\n for (let offset = 0; offset < HARD_CAP; offset += PAGE) {\n const data = await this.request<{ products: ProductLike[] }>(\"GET\", \"/store/products\", {\n // +metadata: Medusa omits product.metadata by default, and that's where lore/lore_sources\n // (and any per-product spec fields) live — without this the CLI/MCP never see them.\n query: { region_id: region.id, limit: PAGE, offset, fields: PRODUCT_FIELDS },\n })\n const page = data.products ?? []\n all.push(...page)\n if (page.length < PAGE) {\n break\n }\n }\n return all\n }\n\n async getProduct(handle: string, opts?: { region?: RegionLike; countryCode?: string }): Promise<ProductLike | null> {\n const region = opts?.region ?? (await this.resolveRegion(opts?.countryCode))\n const data = await this.request<{ products: ProductLike[] }>(\"GET\", \"/store/products\", {\n query: { handle, region_id: region.id, limit: 1, fields: PRODUCT_FIELDS },\n })\n return (data.products ?? [])[0] ?? null\n }\n\n // ----- cart (mutations carry Idempotency-Key) ------------------------------\n\n async createCart(region: RegionLike): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\"POST\", \"/store/carts\", {\n idempotent: true,\n body: { region_id: region.id },\n })\n return data.cart\n }\n\n async addLineItem(cartId: string, variantId: string, quantity: number): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\n \"POST\",\n `/store/carts/${encodeURIComponent(cartId)}/line-items`,\n {\n idempotent: true,\n cartRecovery: true,\n body: { variant_id: variantId, quantity },\n }\n )\n return data.cart\n }\n\n async getCart(cartId: string): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\n \"GET\",\n `/store/carts/${encodeURIComponent(cartId)}`,\n { cartRecovery: true }\n )\n return data.cart\n }\n\n /** Remove a staged line item (bt unstage). Returns the updated cart (Medusa's `parent`). */\n async removeLineItem(cartId: string, itemId: string): Promise<CartLike> {\n const data = await this.request<{ parent?: CartLike; cart?: CartLike }>(\n \"DELETE\",\n `/store/carts/${encodeURIComponent(cartId)}/line-items/${encodeURIComponent(itemId)}`,\n { idempotent: true, cartRecovery: true }\n )\n return (data.parent ?? data.cart) as CartLike\n }\n\n /**\n * Ensure the cart has a shipping method before checkout (BUG 1 — the headless\n * /store/headless/checkout route guards on it, and neither client ever set one).\n *\n * - Idempotent: if the cart already carries a shipping method, no-op (no POST).\n * - Lists options via the STANDARD store route GET /store/shipping-options?cart_id=…\n * (NOT a /headless route — standard routes are always available, no kill-switch).\n * - Picks deterministically: exactly one → use it; several → the cheapest by amount.\n * - The SE launch option is region-scoped (price_type:\"flat\"), so listing by cart_id\n * returns it without a shipping address being set first (confirmed: seed-shipping.ts).\n *\n * TODO(04.5): buyer-choice / intl multiple shipping options — today we auto-pick the\n * cheapest; an explicit buyer chooser belongs to the Phase-6 international pass.\n */\n async ensureCartShippingMethod(cartId: string): Promise<CartLike> {\n // 1. read the authoritative cart; a stale/completed cart → RecoverableCartError (F-B2).\n const cart = await this.getCart(cartId)\n if (cart.shipping_methods && cart.shipping_methods.length > 0) {\n // idempotent: already satisfied.\n return cart\n }\n\n // 2. list options via the STANDARD store route (always available, no kill-switch).\n const data = await this.request<{ shipping_options: ShippingOptionLike[] }>(\n \"GET\",\n \"/store/shipping-options\",\n { query: { cart_id: cartId } }\n )\n const options = data.shipping_options ?? []\n if (options.length === 0) {\n // fail closed: never mint a checkout we know the backend will reject (BUG 1).\n throw new Error(\n \"no shipping option is available for this cart — cannot attach a shipping method\"\n )\n }\n\n // 3. pick deterministically: one → it; several → the cheapest by amount.\n const chosen =\n options.length === 1\n ? options[0]!\n : [...options].sort(\n (a, b) => (a.amount ?? Number.POSITIVE_INFINITY) - (b.amount ?? Number.POSITIVE_INFINITY)\n )[0]!\n\n // 4. attach it — a mutation, so it carries an Idempotency-Key + cart-recovery.\n const res = await this.request<{ cart: CartLike }>(\n \"POST\",\n `/store/carts/${encodeURIComponent(cartId)}/shipping-methods`,\n {\n idempotent: true,\n cartRecovery: true,\n body: { option_id: chosen.id },\n }\n )\n return res.cart\n }\n\n // ----- headless contract (04.5-01) -----------------------------------------\n\n // `channel` is REQUIRED (L11): a default silently mis-attributes analytics for a future caller that\n // forgets it. Both current callers (cli, mcp) pass it explicitly.\n async mintCheckout(cartId: string, email: string | undefined, channel: string): Promise<HeadlessCheckout> {\n return this.request<HeadlessCheckout>(\"POST\", \"/store/headless/checkout\", {\n idempotent: true,\n body: { cart_id: cartId, email, channel },\n })\n }\n\n async poll(sessionId: string): Promise<PollResult> {\n return this.request<PollResult>(\n \"GET\",\n `/store/headless/poll/${encodeURIComponent(sessionId)}`\n )\n }\n\n /** JWT-bound: returns the authenticated customer's own profile (G2). */\n async whoami(): Promise<WhoAmI> {\n // A guest (no/expired JWT) gets a 401/403 — that's the EXPECTED unauthenticated branch the\n // CLI turns into the friendly \"browsing as a guest\" line, NOT an error to dump (BUG 3).\n return this.request<WhoAmI>(\"GET\", \"/store/headless/whoami\", { expected4xx: [401, 403] })\n }\n\n // ----- magic-link auth (04.5-05) -------------------------------------------\n // Passwordless login — magic-link is the only grant (Medusa has no OAuth pairing-grant, G1).\n // request-link emails a single-use link; verify exchanges the link's token for a customer-scoped\n // JWT. The JWT is stored in the OS keychain by the CLI — never here, never in plaintext config (G7/G8).\n\n /** Start a magic-link login. Returns a UNIFORM ack regardless of whether the email exists (G12). */\n async requestMagicLink(email: string): Promise<RequestLinkResult> {\n return this.request<RequestLinkResult>(\"POST\", \"/store/headless/auth/request-link\", {\n idempotent: true,\n body: { email },\n })\n }\n\n /** Exchange a magic-link token for a customer-scoped JWT (the customer is materialized server-side). */\n async verifyMagicLink(token: string): Promise<VerifyLinkResult> {\n return this.request<VerifyLinkResult>(\"POST\", \"/store/headless/auth/verify\", {\n idempotent: true,\n body: { token },\n })\n }\n\n async listDrops(): Promise<DropRow[]> {\n const data = await this.request<{ drops: DropRow[]; count: number }>(\n \"GET\",\n \"/store/headless/drops\"\n )\n return data.drops ?? []\n }\n\n /**\n * File a developer bug report (d14 — `bt report` / MCP report_issue). Returns a correlation\n * reference. Additive: no money/cart path. The backend redacts authoritatively; callers SHOULD\n * pass an already-redacted context (the doctor snapshot). A mutation → carries an Idempotency-Key\n * so a retry can't email the report twice.\n */\n async report(message: string, context?: Record<string, unknown>): Promise<ReportResult> {\n return this.request<ReportResult>(\"POST\", \"/store/headless/report\", {\n idempotent: true,\n body: { message, ...(context !== undefined ? { context } : {}) },\n })\n }\n\n /**\n * The authenticated customer's order history (d13 — `bt log`). JWT-bound; the backend derives the\n * customer from the bearer token and returns MINIMAL fields (status/timeline only — never\n * email/address, F-H3). A guest (no/expired JWT) gets a 401 — the EXPECTED unauthenticated branch\n * we turn into the honest empty history (`[]`), NOT an error to dump (BUG 3).\n */\n async listOrders(): Promise<OrderSummary[]> {\n try {\n const data = await this.request<{ orders: OrderSummary[] }>(\"GET\", \"/store/headless/orders\", {\n expected4xx: [401, 403],\n })\n return data.orders ?? []\n } catch (e) {\n if (e instanceof StoreApiError && (e.status === 401 || e.status === 403)) {\n return []\n }\n throw e\n }\n }\n\n /**\n * Guest order lookup (AUD-8) — POST /store/order-lookup with the order id (display id OR internal\n * id) + the EXACT email in the BODY (never the query string, so the email is not access-logged).\n * The backend returns 404 on any mismatch (no enumeration) → the client surfaces null. Rate-limited\n * server-side; returns minimal status only, never PII (F-H1 / F-H3).\n */\n async getOrder(idOrDisplayId: string | number, email: string): Promise<unknown | null> {\n try {\n return await this.request<unknown>(\"POST\", \"/store/order-lookup\", {\n body: { id: String(idOrDisplayId), email },\n })\n } catch (e) {\n if (e instanceof StoreApiError && e.status === 404) {\n return null\n }\n throw e\n }\n }\n}\n","// Baked-in PUBLIC defaults for the Binary & Thread production channel.\n//\n// These let `npx @binaryandthread/cli` and the MCP server reach the live store with ZERO\n// configuration — the point of the soft-launch tarball (P4.5-7b) and decision #79 (d2).\n// store-client is bundled into both the CLI and the MCP server (tsup `noExternal`), so this\n// is the single source of truth for \"which store\" both clients talk to by default.\n//\n// SAFETY (CLAUDE.md rule 1 / SECURITY T6): the publishable key is PUBLIC BY DESIGN. It is the\n// SAME key embedded in the public storefront's client bundle (binaryandthread.com) and carries\n// no privilege beyond read + cart over the Store API — every dangerous operation lives behind\n// the backend. It is therefore NOT a secret; rule 1 governs secrets, and this is deliberately\n// published. Any env var (BT_API_URL / BT_STORE_URL / BT_PUBLISHABLE_KEY / MEDUSA_*) OVERRIDES\n// these for local dev or staging.\n\n/** The Binary & Thread production Medusa Store API base URL. */\nexport const BT_DEFAULT_STORE_URL = \"https://backend-production-511f5.up.railway.app\"\n\n// The PUBLIC Medusa publishable key (read + cart only; safe to ship — T6). Identical to the key\n// served in the public storefront bundle. NOT a secret (CLAUDE.md rule 1); the inline allow keeps\n// the gitleaks pre-commit hook from flagging a deliberately-published value.\nexport const BT_DEFAULT_PUBLISHABLE_KEY = \"pk_0d7d706860568a0414dff0436aadb9713a2609de3a1b153ef2a6f9694d23f907\" // gitleaks:allow\n","// Image-host allowlist — the canonical source for the WHOLE channel (CLAUDE.md rule 6 / SECURITY G9).\n//\n// We fetch product images ONLY from our own Vercel Blob store. Both the bt CLI (its terminal\n// image ladder) and the Wave-4 MCP (its server-side ImageContent fetch) gate every outbound\n// image fetch on this allowlist. It lives HERE in the shared client — not in the CLI — so the\n// MCP does not take a runtime dependency on the CLI package; the CLI re-exports it from here.\n//\n// HISTORY: a bare `endsWith(allowed)` suffix match was a CRITICAL allowlist-bypass bug fixed in\n// Wave 2 (it would admit `evilblob.vercel-storage.com`). The dot-delimited-subdomain check below\n// is load-bearing security — `host === allowed || host.endsWith(\".\" + allowed)`, never a bare\n// suffix. Do NOT \"simplify\" it.\n\n/**\n * Allowlisted image hosts. We fetch product images ONLY from our own Vercel Blob store\n * (public). NEVER a public temp host (uguu/0x0/catbox/transfer.sh) and NEVER an arbitrary\n * URL — that would be SSRF + a brand-IP leak (CLAUDE.md rule 6).\n */\nexport const BLOB_HOST_ALLOWLIST: readonly string[] = [\n \"blob.vercel-storage.com\", // matches the exact host + *.blob.vercel-storage.com (dot-delimited subdomains only)\n]\n\n/**\n * True if the URL's host is on the Blob allowlist.\n *\n * Exact host OR a true dot-delimited subdomain. NOT a bare suffix: a bare\n * `endsWith(allowed)` would admit `evilblob.vercel-storage.com` (allowlist bypass →\n * SSRF / brand-IP leak, CLAUDE.md rule 6). The dot guard is load-bearing.\n */\nexport function isAllowedImageHost(url: string): boolean {\n let host: string\n try {\n host = new URL(url).hostname.toLowerCase()\n } catch {\n return false\n }\n return BLOB_HOST_ALLOWLIST.some(\n (allowed) => host === allowed || host.endsWith(\".\" + allowed)\n )\n}\n","// Colour resolution — DERIVED from the variant matrix, never hardcoded (CAT-3 forward-compat).\n//\n// The dual-colour catalog (memory: dual-colour-catalog) gives each design ONE product with a\n// Colour × Size variant matrix. A variant's colour comes from, in order of trust:\n// 1. the variant option titled \"Colour\" (Model A: options {Size, Colour}, value title-cased)\n// 2. the variant title, format \"<size> / <Colour>\" (e.g. \"S / White\", \"M / Black\")\n// We NEVER hardcode white/black — future colours (CAT-3) fall out of the data for free.\n//\n// This lives in @binaryandthread/store-client (the canonical types home) so the CLI and the MCP\n// resolve colour the SAME way — written once, tested once.\n\nimport type { ProductLike, VariantLike, ImageLike } from \"./types\"\n\n/** Title-case a colour token for display (\"white\" → \"White\"); leaves multi-word reasonable. */\nexport function displayColour(colour: string): string {\n const c = colour.trim()\n if (!c) return c\n return c.charAt(0).toUpperCase() + c.slice(1).toLowerCase()\n}\n\n/**\n * The colour of a single variant, lowercased — or null if it carries no colour axis (a\n * legacy Size-only product). Reads the \"Colour\" option first, then parses the \"<size> / <Colour>\"\n * title. NEVER assumes a fixed palette.\n */\nexport function variantColour(v: VariantLike): string | null {\n // 1) the explicit Colour option (the authoritative source — memory: never parse the title).\n const opt = (v.options ?? []).find(\n (o) => (o.option?.title ?? \"\").toLowerCase() === \"colour\" || (o.option?.title ?? \"\").toLowerCase() === \"color\"\n )\n if (opt && typeof opt.value === \"string\" && opt.value.trim()) {\n return opt.value.trim().toLowerCase()\n }\n // 2) fall back to the title format \"<size> / <Colour>\".\n const title = (v.title ?? \"\").trim()\n if (title.includes(\"/\")) {\n const parts = title.split(\"/\").map((p) => p.trim())\n const tail = parts[parts.length - 1]\n if (tail) return tail.toLowerCase()\n }\n return null\n}\n\n/**\n * The DISTINCT colours offered by a product, in first-seen order, lowercased. Empty for a\n * legacy Size-only product (no Colour axis) — callers treat that as \"no colour to show\".\n */\nexport function productColours(p: ProductLike): string[] {\n const seen: string[] = []\n for (const v of p.variants ?? []) {\n const c = variantColour(v)\n if (c && !seen.includes(c)) {\n seen.push(c)\n }\n }\n return seen\n}\n\n/** The distinct sizes offered FOR a given colour (lowercased colour match), preserving order. */\nexport function sizesForColour(p: ProductLike, colour: string): string[] {\n const target = colour.trim().toLowerCase()\n const sizes: string[] = []\n for (const v of p.variants ?? []) {\n if (variantColour(v) !== target) continue\n const size = sizeOfVariant(v)\n if (size && !sizes.includes(size)) {\n sizes.push(size)\n }\n }\n return sizes\n}\n\n/** The size label of a variant — the Size option, else the title's leading \"<size>\" segment. */\nexport function sizeOfVariant(v: VariantLike): string | null {\n const opt = (v.options ?? []).find((o) => (o.option?.title ?? \"\").toLowerCase() === \"size\")\n if (opt && typeof opt.value === \"string\" && opt.value.trim()) {\n return opt.value.trim()\n }\n const title = (v.title ?? \"\").trim()\n if (title.includes(\"/\")) {\n const head = title.split(\"/\")[0]?.trim()\n if (head) return head\n }\n // No colour axis → the whole title IS the size (legacy Size-only product).\n return title || null\n}\n\n/**\n * Resolve a variant by size AND colour (the real buyability gap — bt stage / MCP add_to_cart).\n * - size + colour given → exact match on both (case-insensitive).\n * - size only → first variant of that size.\n * - colour only → first variant of that colour.\n * - neither → first variant.\n * Returns undefined when the requested size+colour combination does not exist (the caller MUST\n * surface a clear error — never silently substitute another colour the buyer didn't ask for).\n */\nexport function findVariant(\n p: ProductLike,\n opts: { size?: string; colour?: string } = {}\n): VariantLike | undefined {\n const variants = p.variants ?? []\n const size = opts.size?.trim().toLowerCase()\n const colour = opts.colour?.trim().toLowerCase()\n\n const sizeMatches = (v: VariantLike): boolean => {\n if (!size) return true\n return (sizeOfVariant(v) ?? \"\").toLowerCase() === size || (v.title ?? \"\").toLowerCase() === size\n }\n const colourMatches = (v: VariantLike): boolean => {\n if (!colour) return true\n return variantColour(v) === colour\n }\n\n if (size && colour) {\n // both specified → require BOTH; no fallback to a different colour (a wrong colour is worse\n // than a clear \"that combo isn't available\").\n return variants.find((v) => sizeMatches(v) && colourMatches(v))\n }\n if (colour && !size) {\n return variants.find((v) => colourMatches(v))\n }\n if (size && !colour) {\n // size specified → require it; NO fallback to variants[0] (L10) — a silently-wrong size is worse\n // than undefined, which lets the caller (stage.ts) surface a clear \"no matching variant\" error.\n return variants.find((v) => sizeMatches(v))\n }\n return variants[0]\n}\n\n/**\n * The image URL to render for a given colour. Resolves, in order:\n * 1. the matching variant's own images[] (most precise)\n * 2. a product image whose url/metadata names the colour\n * 3. null → the caller falls back to product.thumbnail + a \"black available\" note.\n * Never throws; a missing colour just yields null.\n */\nexport function imageForColour(p: ProductLike, colour: string): string | null {\n const target = colour.trim().toLowerCase()\n if (!target) return p.thumbnail ?? firstImageUrl(p.images) ?? null\n\n // 1) the colour's variant images.\n for (const v of p.variants ?? []) {\n if (variantColour(v) !== target) continue\n const url = firstImageUrl(v.images)\n if (url) return url\n }\n\n // 2) a product image that names the colour (url substring or metadata.colour tag).\n for (const img of p.images ?? []) {\n const tag = (img.metadata?.[\"colour\"] ?? img.metadata?.[\"color\"] ?? \"\")\n .toString()\n .toLowerCase()\n if (tag === target) {\n const u = (img.url ?? \"\").trim()\n if (u) return u\n }\n }\n for (const img of p.images ?? []) {\n const u = (img.url ?? \"\").trim()\n if (u && new RegExp(`(^|[^a-z])${escapeRegex(target)}([^a-z]|$)`, \"i\").test(u)) {\n return u\n }\n }\n return null\n}\n\nfunction firstImageUrl(images: Array<ImageLike> | null | undefined): string | null {\n for (const img of images ?? []) {\n const u = (img?.url ?? \"\").trim()\n if (u) return u\n }\n return null\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n","// Canonical product-content accessors — the ONE source for a piece's README, spec sheet, and lore,\n// shared by the CLI (`bt view` / `bt blame`), the MCP (`get_product`), and (aspirationally) the\n// storefront PDP. Keeping these here means the three channels can't drift on what a piece \"says\".\n//\n// The spec fields + fallbacks mirror the storefront's buildSpecs (apps/storefront .../templates).\n\nimport type { ProductLike } from \"./types\"\n\n/** One row of the spec sheet. */\nexport interface SpecEntry {\n key: string\n value: string\n}\n\n/** A lore link (a real source behind the pun/idea). */\nexport interface LoreSource {\n url: string\n label: string\n}\n\n/** A piece's lore: the story text + any sources. Empty text = no lore on record. */\nexport interface ProductLore {\n text: string\n sources: LoreSource[]\n}\n\n/** A non-empty string, or the fallback. */\nfunction str(v: unknown, fallback: string): string {\n return typeof v === \"string\" && v.length > 0 ? v : fallback\n}\n\n/** The piece's README — the human description shown on the PDP's README toggle. */\nexport function productReadme(p: ProductLike): string {\n return typeof p.description === \"string\" ? p.description : \"\"\n}\n\n/**\n * The spec sheet — the 6 canonical rows, in PDP order, with the same fallbacks as the storefront's\n * buildSpecs (the single source of truth for what a piece is made of / how it ships).\n */\nexport function productSpecs(p: ProductLike): SpecEntry[] {\n const md = (p.metadata ?? {}) as Record<string, unknown>\n return [\n { key: \"material\", value: str(md.material, \"100% cotton\") },\n { key: \"weight\", value: str(md.weight, \"180 g/m²\") },\n { key: \"fit\", value: str(md.fit, \"Unisex heavyweight crewneck\") },\n { key: \"print\", value: str(md.print, \"Front print\") },\n { key: \"delivery\", value: str(md.delivery, \"Ships in 2–5 business days + transit\") },\n { key: \"care\", value: str(md.care, \"Cold wash, line dry\") },\n ]\n}\n\n/** The piece's lore — the story behind the pun + any real sources. Empty text = none on record. */\nexport function productLore(p: ProductLike): ProductLore {\n const md = (p.metadata ?? {}) as Record<string, unknown>\n const text = typeof md.lore === \"string\" ? md.lore : \"\"\n const raw = Array.isArray(md.lore_sources) ? md.lore_sources : []\n const sources: LoreSource[] = raw\n .filter(\n (s): s is { url: string; label: string } =>\n !!s &&\n typeof s === \"object\" &&\n typeof (s as { url?: unknown }).url === \"string\" &&\n typeof (s as { label?: unknown }).label === \"string\"\n )\n .map((s) => ({ url: s.url, label: s.label }))\n return { text, sources }\n}\n","// The B&T MCP server — stdio transport, registering exactly the channel's deterministic tools.\n//\n// The server is a THIN registrar: each tool is a deterministic wrapper over the shared\n// StoreClient (no free-form execution, no admin op — SECURITY T10/G8). The tool DEFINITIONS\n// (names, input schemas, handlers) live in tools.ts; this module just builds the McpServer and\n// wires the stdio transport so a developer's AI agent can call them over stdin/stdout.\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport type { ZodRawShape } from \"zod\"\nimport type { ImageBlock } from \"./images\"\n\n/** What a tool handler returns — MCP content blocks (image and/or text). */\nexport interface ToolResult {\n content: ImageBlock[]\n /** Set when the tool reports a user-facing error (e.g. a 404 lookup) — not a transport error. */\n isError?: boolean\n /**\n * Machine-readable result (ids/handles like cart_id/session_id/order_id) so an agent can chain\n * tools WITHOUT regexing the prose (d9). Already redacted (G10). Mirrors the human text block.\n */\n structuredContent?: Record<string, unknown>\n}\n\n/**\n * A deterministic tool definition. `inputSchema` is a Zod raw shape (the SDK turns it into a JSON\n * schema for the agent). The handler receives the parsed args and returns content blocks. Eggs\n * (VOICE §3.2 M2/M3) may live in the `description` text but NEVER alter behavior.\n */\nexport interface ToolDef<S extends ZodRawShape = ZodRawShape> {\n name: string\n description: string\n inputSchema: S\n handler: (args: Record<string, unknown>) => Promise<ToolResult>\n}\n\nexport const SERVER_INFO = {\n name: \"binary-and-thread\",\n version: \"0.1.0\",\n} as const\n\n/**\n * Build an McpServer and register every tool. Returns the server plus the registered tool names\n * (so callers/tests can assert exactly the 10 tools are present). Pure — no transport, no I/O.\n */\nexport function buildServer(tools: ToolDef[]): { server: McpServer; toolNames: string[] } {\n const server = new McpServer(SERVER_INFO)\n const toolNames: string[] = []\n // The SDK's registerTool generic over a ZodRawShape input schema infers excessively deeply\n // (TS2589); we register through a structurally-typed view of the method to keep the call site\n // simple. The runtime contract is unchanged — name, { description, inputSchema }, handler.\n const register = server.registerTool.bind(server) as unknown as (\n name: string,\n config: { description: string; inputSchema: ZodRawShape },\n cb: (args: Record<string, unknown>) => Promise<{\n content: ImageBlock[]\n isError?: boolean\n structuredContent?: Record<string, unknown>\n }>\n ) => void\n for (const t of tools) {\n register(\n t.name,\n { description: t.description, inputSchema: t.inputSchema },\n // The SDK validates args against inputSchema before calling us. We hand the parsed args to\n // the deterministic handler and return its content blocks (+ optional structuredContent, d9).\n async (args: Record<string, unknown>) => {\n const res = await t.handler(args ?? {})\n return {\n content: res.content,\n ...(res.isError ? { isError: true } : {}),\n ...(res.structuredContent ? { structuredContent: res.structuredContent } : {}),\n }\n }\n )\n toolNames.push(t.name)\n }\n return { server, toolNames }\n}\n\n/** Build the server with the real tools and start it on stdio. Used by bin.ts. */\nexport async function startServer(tools: ToolDef[]): Promise<McpServer> {\n const { server } = buildServer(tools)\n const transport = new StdioServerTransport()\n await server.connect(transport)\n return server\n}\n","// The 10 deterministic MCP tools over the SHARED StoreClient (SECURITY G8/T10; ROBUSTNESS §4.2/4.3).\n//\n// These are thin wrappers — no free-form execution, no admin op, no money self-completion. They\n// reuse the same idempotency / region-resolution / retry / redaction that the CLI uses (it all\n// lives inside the StoreClient — written once, contract-tested once).\n//\n// The hard invariants this file enforces (the MCP red-team gates):\n// - checkout (G8 confused-deputy / F-I4): returns {checkout_url, session_id} INSTANTLY. It NEVER\n// polls, NEVER opens a browser, NEVER sets a shipping address. Its result text tells the human\n// to open the link and explicitly says \"I can't complete this for you\". The human pays on the\n// storefront (Stripe Elements) — payment details NEVER enter the chat. check_payment polls; the\n// agent re-calls at its own cadence (async handle-ID).\n// (NB: there is deliberately NO payment-detail field anywhere in this package; the security\n// verify greps for those field tokens and must come back clean.)\n// - list views are TEXT-ONLY (no per-item images — token-bloat guard, ROBUSTNESS §4.3).\n// - every backend string is sanitize()'d (G8); every output object is redactForOutput()'d (G10).\n// - get_order_status returns status/timeline only — NO address (F-H3); id + exact-email, 404 on\n// miss (F-H1).\n\nimport { z } from \"zod\"\nimport {\n RecoverableCartError,\n StoreApiError,\n productColours,\n sizesForColour,\n findVariant,\n productSpecs,\n productLore,\n productReadme,\n} from \"@binaryandthread/store-client\"\nimport type {\n StoreClient,\n ProductLike,\n CartLike,\n RegionLike,\n} from \"@binaryandthread/store-client\"\nimport { sanitize, redactForOutput } from \"./sanitize\"\nimport { buildImageContent } from \"./images\"\nimport type { ImageBlock } from \"./images\"\nimport type { ToolDef, ToolResult } from \"./server\"\nimport { TEAPOT_TEXT, FORTUNES, fortune } from \"./eggs\"\n\nexport interface ToolsOpts {\n /** ISO-2 country code to resolve pricing (never a hardcoded currency literal). */\n region?: string\n}\n\n// ----- result helpers ---------------------------------------------------------\n\nfunction text(s: string): ToolResult {\n return { content: [{ type: \"text\", text: s }] }\n}\n\nfunction errorText(s: string): ToolResult {\n return { content: [{ type: \"text\", text: s }], isError: true }\n}\n\n/** A text block PLUS machine-readable structuredContent (ids/handles) for agent chaining (d9). */\nfunction result(textBody: string, structured: Record<string, unknown>): ToolResult {\n return {\n content: [{ type: \"text\", text: textBody }],\n structuredContent: redactForOutput(structured) as Record<string, unknown>,\n }\n}\n\n/** Price line — currency comes from the variant/region, NEVER a literal (F-A2/INV-11). */\nfunction fmtPrice(amount: number | null | undefined, currency: string | null | undefined): string {\n if (amount == null) {\n return \"—\"\n }\n const cur = (currency ?? \"\").toUpperCase()\n return cur ? `${amount} ${cur}` : `${amount}`\n}\n\nfunction firstPrice(p: ProductLike): { amount: number | null; currency: string | null } {\n const v = (p.variants ?? [])[0]\n return {\n amount: v?.calculated_price?.calculated_amount ?? null,\n currency: v?.calculated_price?.currency_code ?? null,\n }\n}\n\nfunction cartSummary(cart: CartLike, region?: RegionLike): string {\n const lines: string[] = []\n lines.push(`cart ${cart.id}`)\n const items = cart.items ?? []\n if (items.length === 0) {\n lines.push(\" (empty)\")\n } else {\n for (const li of items) {\n const title = sanitize(li.product_title ?? li.title ?? \"item\", 80)\n lines.push(` ${li.quantity}x ${title} — ${fmtPrice(li.total, cart.currency_code ?? region?.currency_code)}`)\n }\n }\n lines.push(` total ${fmtPrice(cart.total, cart.currency_code ?? region?.currency_code)} incl. VAT`)\n return lines.join(\"\\n\")\n}\n\n// ----- the 10 tools ------------------------------------------------------------\n\n/** Build the 10 tool definitions bound to a StoreClient. */\nexport function makeTools(client: StoreClient, opts: ToolsOpts = {}): ToolDef[] {\n const regionCC = opts.region\n\n // list_products — TEXT-ONLY catalog (no per-item images — token-bloat guard, §4.3).\n const list_products: ToolDef = {\n name: \"list_products\",\n description:\n \"List the pieces in stock (text-only — no images, to keep the agent's context lean). \" +\n \"Returns each product's handle, price incl. VAT, and stage. Use get_product for one item's image.\",\n inputSchema: { stage: z.string().optional().describe(\"filter by channel stage, e.g. 'main' or 'next'\") },\n handler: async (args): Promise<ToolResult> => {\n const region = await client.resolveRegion(regionCC)\n const products = await client.listProducts({ region })\n const stage = typeof args.stage === \"string\" ? args.stage.toLowerCase() : undefined\n const rows = products\n .map((p) => ({ p, stage: String(p.metadata?.[\"channel_stage\"] ?? \"main\") }))\n .filter((r) => (stage ? r.stage.toLowerCase() === stage : true))\n if (rows.length === 0) {\n return result(\"nothing in stock on this branch yet.\", { products: [] })\n }\n const lines = rows.map(({ p, stage }) => {\n const { amount, currency } = firstPrice(p)\n const handle = sanitize(p.handle ?? p.id, 60)\n const title = sanitize(p.title ?? \"\", 80)\n return `${handle} ${fmtPrice(amount, currency)} incl. VAT [${stage}] ${title}`.trimEnd()\n })\n const productList = rows.map(({ p, stage }) => {\n const { amount, currency } = firstPrice(p)\n return { handle: p.handle ?? p.id, title: p.title ?? \"\", price: amount, currency, stage }\n })\n return result(lines.join(\"\\n\"), { products: productList })\n },\n }\n\n // get_product — details + the ONE product image (ImageContent + Blob URL floor).\n const get_product: ToolDef = {\n name: \"get_product\",\n // M3 ★ — a hidden line for the schema-readers (devs read tool descriptions). Rewards the\n // contributor instinct + hints at a real feature (the next branch / drops). Never alters behaviour.\n description:\n \"Get one piece in full: its readme, the spec sheet (material/weight/fit/print/delivery/care), \" +\n \"sizes + price incl. VAT, the lore (the story behind it, with sources), and its image (rendered + \" +\n \"the Blob URL as a text floor). // you read the schema. that's the contributor instinct. ask me about the next branch.\",\n inputSchema: { handle: z.string().describe(\"the product handle/slug\") },\n handler: async (args): Promise<ToolResult> => {\n const handle = String(args.handle ?? \"\")\n const region = await client.resolveRegion(regionCC)\n const product = await client.getProduct(handle, { region })\n if (!product) {\n return errorText(`no product with handle \"${sanitize(handle, 60)}\". try list_products.`)\n }\n const lines: string[] = []\n lines.push(sanitize(product.title ?? handle, 120))\n if (product.description) {\n lines.push(sanitize(product.description, 400))\n }\n const fabric = product.metadata?.[\"fabric\"]\n if (typeof fabric === \"string\") {\n lines.push(`fabric: ${sanitize(fabric, 80)}`)\n }\n // A3 — available colours, DERIVED from the variants (never hardcoded). Sizes are listed per\n // colour when there's a colour axis, else a flat list (legacy Size-only product).\n const colours = productColours(product)\n if (colours.length > 0) {\n lines.push(`available colours: ${colours.map((c) => sanitize(c, 20)).join(\", \")}`)\n for (const c of colours) {\n const cs = sizesForColour(product, c).map((s) => sanitize(s, 20))\n lines.push(` ${sanitize(c, 20)}: ${cs.join(\" \")}`)\n }\n } else {\n const sizes = (product.variants ?? []).map((v) => sanitize(v.title ?? \"?\", 20))\n lines.push(`sizes: ${sizes.join(\" \")}`)\n }\n const v0 = (product.variants ?? [])[0]\n if (v0?.calculated_price) {\n lines.push(`price: ${fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code)} incl. VAT`)\n }\n // The full spec sheet (the PDP's technical block) + the lore — so the agent knows the piece,\n // not just its price. Single-sourced from store-client (matches the storefront).\n const specs = productSpecs(product)\n lines.push(\"specs:\")\n for (const s of specs) {\n lines.push(` ${sanitize(s.key, 20)}: ${sanitize(s.value, 120)}`)\n }\n const lore = productLore(product)\n if (lore.text) {\n lines.push(`lore: ${sanitize(lore.text, 600)}`)\n for (const src of lore.sources) {\n lines.push(` source: ${sanitize(src.label, 80)} (${sanitize(src.url, 300)})`)\n }\n }\n const content: ImageBlock[] = [{ type: \"text\", text: lines.join(\"\\n\") }]\n if (product.thumbnail) {\n const img = await buildImageContent(product.thumbnail)\n content.push(...img)\n }\n return {\n content,\n structuredContent: redactForOutput({\n handle: product.handle ?? handle,\n title: product.title ?? \"\",\n price: v0?.calculated_price?.calculated_amount ?? null,\n currency: v0?.calculated_price?.currency_code ?? null,\n colours,\n image_url: product.thumbnail ?? null,\n // the PDP content, machine-readable: readme + the spec sheet + the lore (+ sources).\n readme: productReadme(product),\n specs,\n lore,\n }) as Record<string, unknown>,\n }\n },\n }\n\n // add_to_cart — create-or-reuse a cart; return the cart_id handle + the server cart.\n const add_to_cart: ToolDef = {\n name: \"add_to_cart\",\n description:\n \"Add a piece to a cart. Pass cart_id to reuse an existing cart (the durable handle), or omit it \" +\n \"to start a fresh one. Returns the cart_id and the server-side cart summary (price incl. VAT).\",\n inputSchema: {\n handle: z.string().optional().describe(\"product handle (resolves a variant by size + colour)\"),\n variant_id: z.string().optional().describe(\"a specific variant id (overrides handle/size/colour)\"),\n size: z.string().optional().describe(\"size label, e.g. M / L / XL\"),\n colour: z.string().optional().describe(\"piece colour, e.g. white / black (default: the product's first colour)\"),\n qty: z.number().int().positive().max(20).optional().describe(\"quantity (default 1)\"),\n cart_id: z.string().optional().describe(\"reuse this cart; omit to create a new one\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const region = await client.resolveRegion(regionCC)\n const qty = typeof args.qty === \"number\" ? args.qty : 1\n\n // resolve the variant id\n let variantId = typeof args.variant_id === \"string\" ? args.variant_id : undefined\n if (!variantId) {\n const handle = typeof args.handle === \"string\" ? args.handle : \"\"\n if (!handle) {\n return errorText(\"add_to_cart needs a variant_id or a handle (+ optional size/colour).\")\n }\n const product = await client.getProduct(handle, { region })\n if (!product) {\n return errorText(`no product with handle \"${sanitize(handle, 60)}\".`)\n }\n // A3 — resolve by size AND colour (DERIVED, never hardcoded). colour defaults to the\n // product's first colour; a missing size+colour combo is a CLEAR error (no substitution).\n const size = typeof args.size === \"string\" ? args.size : undefined\n const colours = productColours(product)\n const colour =\n typeof args.colour === \"string\" ? args.colour : colours.length > 0 ? colours[0] : undefined\n const variant = findVariant(product, { size, colour })\n if (!variant) {\n const have = colours.length ? ` available colours: ${colours.map((c) => sanitize(c, 20)).join(\", \")}.` : \"\"\n return errorText(\n `no variant for \"${sanitize(handle, 60)}\"${size ? ` size ${sanitize(size, 20)}` : \"\"}` +\n `${colour ? ` (${sanitize(colour, 20)})` : \"\"}.${have}`\n )\n }\n variantId = variant.id\n }\n\n // create-or-reuse the cart; a stale/completed cart → make a fresh one (F-B2).\n const reuse = typeof args.cart_id === \"string\" ? args.cart_id : undefined\n const addTo = async (cartId: string): Promise<CartLike> => client.addLineItem(cartId, variantId!, qty)\n let cart: CartLike\n try {\n const targetId = reuse ?? (await client.createCart(region)).id\n cart = await addTo(targetId)\n } catch (e) {\n if (e instanceof RecoverableCartError && reuse) {\n // the reused cart was stale/completed → silently make a fresh one (the handle rotates).\n const fresh = await client.createCart(region)\n cart = await addTo(fresh.id)\n } else {\n throw e\n }\n }\n const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0)\n return result(\n `added.\\ncart_id: ${cart.id} (reuse this handle on the next add / checkout)\\n${cartSummary(cart, region)}`,\n {\n cart_id: cart.id,\n total: cart.total ?? null,\n currency: cart.currency_code ?? region.currency_code ?? null,\n item_count: itemCount,\n }\n )\n },\n }\n\n // get_cart — the authoritative server cart summary.\n const get_cart: ToolDef = {\n name: \"get_cart\",\n description: \"Show a cart's current contents and total (price incl. VAT), straight from the server.\",\n inputSchema: { cart_id: z.string().describe(\"the cart handle from add_to_cart\") },\n handler: async (args): Promise<ToolResult> => {\n const cartId = String(args.cart_id ?? \"\")\n const region = await client.resolveRegion(regionCC)\n try {\n const cart = await client.getCart(cartId)\n const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0)\n return result(cartSummary(cart, region), {\n cart_id: cart.id,\n total: cart.total ?? null,\n currency: cart.currency_code ?? region.currency_code ?? null,\n item_count: itemCount,\n })\n } catch (e) {\n if (e instanceof RecoverableCartError) {\n return errorText(\"that cart is no longer valid (expired or already committed). add an item to start a fresh one.\")\n }\n throw e\n }\n },\n }\n\n // checkout — async handle-ID hand-off. Returns INSTANTLY. NEVER polls / opens a browser /\n // sets an address. Payment details NEVER enter the chat (G8 confused-deputy / F-I4).\n const checkout: ToolDef = {\n name: \"checkout\",\n description:\n \"Mint a checkout hand-off for a cart. Returns a checkout_url + a session_id INSTANTLY — it does \" +\n \"NOT poll and does NOT complete the purchase. The human must open the link in their own browser \" +\n \"to enter their payment + ship-to details on the storefront (those details never enter this chat). \" +\n \"After they pay, call check_payment with the session_id to see the status.\",\n inputSchema: {\n cart_id: z.string().describe(\"the cart handle to check out\"),\n email: z.string().optional().describe(\"the buyer's email for the receipt (optional)\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const cartId = String(args.cart_id ?? \"\")\n const email = typeof args.email === \"string\" ? args.email : undefined\n // BUG 1: the headless checkout route requires the cart to carry a shipping method. Attach\n // one first (idempotent in the client; auto-picks the only/cheapest region option — it\n // sets a SHIPPING method, never a payment/address, so the G8 boundary is untouched). Fail\n // CLOSED: if no option is available we return an error and NEVER mint a checkout link.\n try {\n await client.ensureCartShippingMethod(cartId)\n } catch (e) {\n if (e instanceof RecoverableCartError) {\n return errorText(\"that cart is no longer valid (expired or already committed). add an item to start a fresh one.\")\n }\n return errorText(\"couldn't attach a shipping method to this cart — checkout isn't available for it right now.\")\n }\n // mintCheckout is idempotent in the client. We return its result DIRECTLY — no poll, no\n // browser, no address. This is the entire G8 boundary: the agent cannot self-complete.\n const { checkout_url, session_id } = await client.mintCheckout(cartId, email, \"mcp\")\n const body = [\n \"checkout ready. open this link in your browser to pay:\",\n ` ${checkout_url}`,\n \"\",\n \"payment details are entered on the storefront (Stripe) — they never enter this chat, and\",\n \"I can't complete this for you. once you've paid, call check_payment with:\",\n ` session_id: ${session_id}`,\n \"\",\n // brand-delight #9 — the deadpan flourish. The boundary stays honest (the line above), then\n // one dry true line: an agent can do everything up to the card. The card is the human's.\n \"I staged it, priced it, and walked you to the door. the card swipe is yours — by design.\",\n ].join(\"\\n\")\n // redact defensively (the url/session id carry no secret, but the redactor is the rule).\n return result(redactForOutput(body) as string, { checkout_url, session_id })\n },\n }\n\n // check_payment — poll ONCE; the agent re-calls at its own cadence (async handle-ID, F-I4).\n const check_payment: ToolDef = {\n name: \"check_payment\",\n description:\n \"Check a checkout's payment status ONCE (call again later to re-check — this never blocks/loops). \" +\n \"Returns the status and, once placed, the order_id.\",\n inputSchema: { session_id: z.string().describe(\"the session_id from checkout\") },\n handler: async (args): Promise<ToolResult> => {\n const sessionId = String(args.session_id ?? \"\")\n const res = await client.poll(sessionId)\n const out = redactForOutput({ status: res.status, order_id: res.order_id })\n const human =\n res.status === \"placed\"\n ? `paid — order placed. order_id: ${out.order_id ?? \"—\"}`\n : `status: ${out.status} (call check_payment again in a moment if still pending)`\n return { content: [{ type: \"text\", text: human }], structuredContent: out as Record<string, unknown> }\n },\n }\n\n // get_order_status — status/timeline TEXT only; NO address (F-H3); id + exact-email, 404 on miss (F-H1).\n const get_order_status: ToolDef = {\n name: \"get_order_status\",\n description:\n \"Look up an order's status by its display id + the EXACT email on the order. Returns status only \" +\n \"(no shipping address). A wrong id/email returns not-found (no enumeration).\",\n inputSchema: {\n display_id: z.string().describe(\"the order's display id (e.g. 1042)\"),\n email: z.string().describe(\"the exact email on the order\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const displayId = String(args.display_id ?? \"\")\n const email = String(args.email ?? \"\")\n // AUD-8: the shared client now POSTs to /store/order-lookup with the email in the BODY\n // (no longer a query string); getOrder still surfaces the 404→null (no-enumeration) contract.\n let looked: unknown | null\n try {\n looked = await client.getOrder(displayId, email)\n } catch (e) {\n if (e instanceof StoreApiError && e.status === 404) {\n looked = null\n } else {\n throw e\n }\n }\n if (!looked) {\n return errorText(`no order matches that id + email.`)\n }\n // Extract ONLY status/timeline fields — NEVER the shipping address (F-H3).\n const order = (looked as { order?: Record<string, unknown> }).order ?? (looked as Record<string, unknown>)\n const status = sanitize(String(order[\"status\"] ?? order[\"fulfillment_status\"] ?? \"unknown\"), 60)\n const display = sanitize(String(order[\"display_id\"] ?? displayId), 30)\n const lines = [`order #${display}`, `status: ${status}`]\n const fulfillment = order[\"fulfillment_status\"]\n if (typeof fulfillment === \"string\" && fulfillment) {\n lines.push(`fulfillment: ${sanitize(fulfillment, 60)}`)\n }\n // The output is redacted — and we never selected the address, so no PII can leak (G10/F-H3).\n return result(redactForOutput(lines.join(\"\\n\")) as string, {\n display_id: order[\"display_id\"] ?? display,\n status,\n fulfillment_status: typeof fulfillment === \"string\" ? fulfillment : null,\n })\n },\n }\n\n // report_issue — file a bug report / feedback to the maintainers (d14). ADDITIVE: it sends NO\n // payment/PII and cannot touch any order — the one way an agent can flag a problem. The message +\n // optional context are redacted SERVER-SIDE before they're emailed (G10); we return a reference.\n const report_issue: ToolDef = {\n name: \"report_issue\",\n description:\n \"File a bug report or feedback to the maintainers. Pass a short message (and optional \" +\n \"structured context like versions/ids). Returns a reference id. It sends no payment details \" +\n \"and cannot change any order — it just files the report. Tokens/emails are redacted server-side.\",\n inputSchema: {\n message: z.string().describe(\"what went wrong / the feedback (one or two sentences)\"),\n context: z\n // zod v4 requires an explicit key type: record(keyType, valueType). (Was record(value) —\n // a pre-existing TS2554 that tsup transpiled past; fixed so `npm run typecheck` is clean.)\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"optional structured context (versions, ids) — tokens/emails are redacted server-side\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const message = String(args.message ?? \"\").trim()\n if (!message) {\n return errorText(\"report_issue needs a non-empty message describing the problem.\")\n }\n const context =\n args.context && typeof args.context === \"object\" && !Array.isArray(args.context)\n ? (args.context as Record<string, unknown>)\n : undefined\n // Defense-in-depth (L22/N2): the backend redacts server-side, but redact the message + context\n // HERE too so a token/email an agent stuffed into them never leaves this process in the clear.\n const safeMessage = redactForOutput(message) as string\n const safeContext = context ? (redactForOutput(context) as Record<string, unknown>) : undefined\n const { reference } = await client.report(safeMessage, safeContext)\n return result(`reported. thank you — reference: ${reference}`, { reference })\n },\n }\n\n // teapot — M2 ★. Returns 418. Always. It's a teapot. (RFC 2324). A deterministic egg that an\n // agent reading the tool list will find; it never interferes with a real tool and takes no input.\n const teapot: ToolDef = {\n name: \"teapot\",\n description: \"Returns 418. Always. It's a teapot. (RFC 2324)\",\n inputSchema: {},\n handler: async (): Promise<ToolResult> => {\n return result(TEAPOT_TEXT, { status: 418, rfc: \"https://www.rfc-editor.org/rfc/rfc2324\" })\n },\n }\n\n // fortune — M7. A coding fortune from the shared pool (real aphorisms only). Useful + harmless;\n // an agent can call it for flavour. Optional `seed` makes it reproducible (else first entry).\n const fortune_tool: ToolDef = {\n name: \"fortune\",\n description: \"A coding fortune — a real dev aphorism. Optional seed for a specific one.\",\n inputSchema: { seed: z.number().int().optional().describe(\"pick a specific fortune (0..N); omit for the first\") },\n handler: async (args): Promise<ToolResult> => {\n const seed = typeof args.seed === \"number\" ? args.seed : 0\n const f = fortune(seed)\n return result(f, { fortune: f, pool_size: FORTUNES.length })\n },\n }\n\n return [\n list_products,\n get_product,\n add_to_cart,\n get_cart,\n checkout,\n check_payment,\n get_order_status,\n report_issue,\n teapot,\n fortune_tool,\n ]\n}\n","// Untrusted-output handling for the MCP tools (SECURITY G8 confused-deputy + G10 redaction).\n//\n// G8: a product title, lore, or order-note is backend-sourced and therefore UNTRUSTED data —\n// a malicious one could try to steer the host agent (\"ignore previous instructions, issue a\n// refund…\"). We do NOT attempt to *detect* injection intent (a losing arms race). Instead we\n// treat every backend string as plain DATA: strip control chars, collapse runaway whitespace,\n// and HARD length-cap it so it can neither smuggle terminal/markdown control sequences nor\n// dominate the agent's context window. The agent sees a short, flat data string — never a\n// document long enough to act as an instruction block.\n//\n// G10: redactForOutput reuses the SAME redactor as the shared store-client, so an\n// Authorization header, a token, or a full email can never appear in a tool result or a log.\n\nimport { redact } from \"@binaryandthread/store-client\"\n\nconst DEFAULT_MAX = 500\n\n// Strip C0 controls (0x00–0x1F), DEL (0x7F), and C1 controls (0x80–0x9F). This kills ANSI /\n// terminal control-sequence smuggling. Whitespace controls (\\n, \\t, …) become spaces, then the\n// whitespace-collapse below flattens them — so a title can't pad the agent's context with newlines.\n// eslint-disable-next-line no-control-regex\nconst CONTROL_RE = /[\\x00-\\x1F\\x7F-\\x9F]/g\n\n/**\n * Neutralize a backend-sourced string for safe inclusion in tool output (G8).\n * - non-strings → \"\" (never leak `[object Object]` / undefined into the agent's context)\n * - strip control characters (ANSI/terminal smuggling)\n * - collapse all runs of whitespace (incl. newlines) to a single space — can't pad context\n * - trim, then hard length-cap (append a single ellipsis when truncated)\n */\nexport function sanitize(s: string, max = DEFAULT_MAX): string {\n if (typeof s !== \"string\") {\n return \"\"\n }\n const stripped = s\n .replace(CONTROL_RE, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n if (stripped.length <= max) {\n return stripped\n }\n return stripped.slice(0, max) + \"…\"\n}\n\n/**\n * Deep-redact any value before it goes into a tool result or a log (G10). Reuses the\n * store-client redactor: masks Authorization/token/secret keys, strips Bearer tokens from\n * free strings, masks emails. Never throws (handles cyclic input).\n */\nexport function redactForOutput<T>(obj: T): T {\n return redact(obj)\n}\n","// MCP image path — ImageContent + the Blob-URL text floor, SSRF/token-DoS safe (SECURITY G9,\n// CLAUDE.md rule 6, ROBUSTNESS §6 / F-A3).\n//\n// Why BOTH an image block AND a URL text block: Claude renders ImageContent natively on\n// claude.ai / the API, but Claude Code has a base64-bloat bug (#31208, 15–25k tokens/image) and\n// can fail to render it. So the Blob URL is ALWAYS emitted as a text block too — the reliable\n// floor (the buy is never blocked on rendering).\n//\n// SECURITY G9 — the fetch is the dangerous part, so it is gated HARD:\n// 1. host allowlist (reused verbatim from @binaryandthread/store-client — the SAME constant the\n// CLI uses) BEFORE any network call. A non-allowlisted host / look-alike subdomain → no fetch.\n// 2. scheme MUST be https — a `data:` or `http:` URL → no fetch (no inline payload, no plaintext).\n// 3. a byte cap (≤2MB) + a fetch timeout — over-cap or slow → URL-text-only (token-DoS guard).\n// 4. content-type must be an image — refuse to base64 a non-image payload.\n// On ANY of those (or any failure) we degrade to URL-text-only and fetch NOTHING. It NEVER throws.\n\nimport { isAllowedImageHost } from \"@binaryandthread/store-client\"\n\n/** ≤2MB — over this we return the URL only (token-DoS guard, G9). */\nexport const IMAGE_BYTE_CAP = 2 * 1024 * 1024\n/** Fetch timeout in ms — a slow/hung host must not stall the tool (a list/view is read-mostly). */\nexport const IMAGE_FETCH_TIMEOUT_MS = 5000\n\n/** An MCP content block — either an inline image or a text line. */\nexport type ImageBlock =\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"text\"; text: string }\n\nexport interface BuildImageOpts {\n /** Inject a fetch impl (tests). Defaults to global fetch. */\n fetchImpl?: typeof fetch\n /** Override the byte cap (tests). */\n byteCap?: number\n /** Override the timeout (tests). */\n timeoutMs?: number\n}\n\n/** The URL-text-only floor — always carries the Blob URL so the agent can hand it to the human. */\nfunction urlOnly(blobUrl: string): ImageBlock[] {\n return [{ type: \"text\", text: blobUrl }]\n}\n\n/** The \"we couldn't render it\" floor — still carries the URL (never blocks the buy). */\nfunction unavailable(blobUrl: string): ImageBlock[] {\n return [{ type: \"text\", text: `(image unavailable) ${blobUrl}` }]\n}\n\n/**\n * Build the content blocks for a single product image. Returns the Blob URL text floor in EVERY\n * case; adds an ImageContent block ONLY when the URL is an allowlisted-host https Blob URL that\n * fetches successfully within the byte cap + timeout as a real image. NEVER throws, NEVER fetches\n * a non-allowlisted / data: / non-https URL.\n */\nexport async function buildImageContent(\n blobUrl: string,\n opts: BuildImageOpts = {}\n): Promise<ImageBlock[]> {\n if (typeof blobUrl !== \"string\" || blobUrl.trim().length === 0) {\n return urlOnly(String(blobUrl ?? \"\"))\n }\n\n // (1) + (2): host allowlist + https-only, BEFORE any network call (G9 / rule 6).\n // isAllowedImageHost already returns false for an unparseable URL; we also require https.\n let scheme: string\n try {\n scheme = new URL(blobUrl).protocol\n } catch {\n return urlOnly(blobUrl)\n }\n if (scheme !== \"https:\") {\n return urlOnly(blobUrl) // data:, http:, etc. — never fetch (no SSRF, no plaintext, no inline payload)\n }\n if (!isAllowedImageHost(blobUrl)) {\n return urlOnly(blobUrl) // non-allowlisted host or look-alike subdomain — never fetch\n }\n\n const fetchImpl = opts.fetchImpl ?? (globalThis.fetch as typeof fetch)\n const byteCap = opts.byteCap ?? IMAGE_BYTE_CAP\n const timeoutMs = opts.timeoutMs ?? IMAGE_FETCH_TIMEOUT_MS\n\n // (3) + (4): the gated fetch. Any failure → URL-text floor (never throws — F-A3).\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const res = await fetchImpl(blobUrl as unknown as string, { signal: controller.signal })\n if (!res.ok) {\n return urlOnly(blobUrl)\n }\n const mimeType = (res.headers.get(\"content-type\") ?? \"\").split(\";\")[0]!.trim().toLowerCase()\n if (!mimeType.startsWith(\"image/\")) {\n return urlOnly(blobUrl) // refuse to base64 a non-image payload\n }\n // (M4) Reject an over-cap body BEFORE buffering it into memory, when the server declares its size.\n // The post-buffer check below still applies (Content-Length can be absent or wrong) — this just\n // avoids reading a known-too-large body at all.\n const declaredLen = Number.parseInt(res.headers.get(\"content-length\") ?? \"\", 10)\n if (Number.isFinite(declaredLen) && declaredLen > byteCap) {\n return urlOnly(blobUrl)\n }\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.byteLength === 0 || buf.byteLength > byteCap) {\n return urlOnly(blobUrl) // empty or over-cap → token-DoS guard\n }\n return [\n { type: \"image\", data: buf.toString(\"base64\"), mimeType },\n { type: \"text\", text: blobUrl }, // the reliable floor (bug #31208)\n ]\n } catch {\n return unavailable(blobUrl)\n } finally {\n clearTimeout(timer)\n }\n}\n","// MCP egg copy (VOICE §3.2). The MCP surface is read by AGENTS and the humans driving them, so its\n// eggs live in tool descriptions (devs read schemas) and in deterministic tool payloads. They NEVER\n// interfere with a real tool and never touch money.\n//\n// This mirrors the CLI's governed copy (packages/cli/src/eggs.ts). It is intentionally duplicated:\n// the MCP package composes over store-client only (not the CLI), so each channel owns its copy.\n// Brand voice (memory brand-voice-no-pod-no-tshirt): never \"t-shirt\"/\"tee\"/\"print\"/Gelato.\n\n/** M2 ★ — the bt.teapot tool body. Returns 418. Always. It's a teapot. (RFC 2324) */\nexport const TEAPOT_TEXT = \"418 i'm a teapot. we make clothing, not coffee. (RFC 2324)\"\n\n/** C10 / M7 — the shared fortune pool. REAL dev aphorisms only (gate 1: never invented). */\nexport const FORTUNES: ReadonlyArray<string> = [\n '// \"there are only two hard things in computer science: cache invalidation and naming things.\" — phil karlton',\n '// \"premature optimization is the root of all evil.\" — donald knuth',\n '// \"weeks of programming can save you hours of planning.\"',\n '// \"make it work, make it right, make it fast.\" — kent beck',\n '// \"talk is cheap. show me the code.\" — linus torvalds',\n '// \"given enough eyeballs, all bugs are shallow.\" — linus\\'s law',\n '// \"always code as if the person maintaining it is a violent psychopath who knows where you live.\" — john woods',\n '// \"it works on my machine.\"',\n]\n\n/** Deterministic fortune by seed (reproducible for tests + screenshots). */\nexport function fortune(seed = 0): string {\n const n = Number.isFinite(seed) ? Math.abs(Math.floor(seed)) : 0\n return FORTUNES[n % FORTUNES.length]!\n}\n","// Resolve the MCP server's transport config from the environment, falling back to the baked-in\n// PUBLIC production defaults so a host MCP config needs ZERO env to reach the live store\n// (d2 / P4.5-7b). Env ALWAYS wins (set BT_STORE_URL / BT_PUBLISHABLE_KEY for local dev / staging).\n// The JWT (BT_TOKEN) and region (BT_REGION) stay env-only — never baked, never read from chat (T8).\n\nimport { BT_DEFAULT_STORE_URL, BT_DEFAULT_PUBLISHABLE_KEY } from \"@binaryandthread/store-client\"\n\nexport interface ResolvedServerConfig {\n baseUrl: string\n publishableKey: string\n jwt?: string\n region?: string\n}\n\nexport function resolveServerConfig(env: NodeJS.ProcessEnv = process.env): ResolvedServerConfig {\n return {\n baseUrl: env.BT_STORE_URL || BT_DEFAULT_STORE_URL,\n publishableKey: env.BT_PUBLISHABLE_KEY || BT_DEFAULT_PUBLISHABLE_KEY,\n jwt: env.BT_TOKEN || undefined,\n region: env.BT_REGION || undefined,\n }\n}\n","// npx @binaryandthread/mcp — the stdio entrypoint.\n//\n// Config comes from the ENVIRONMENT only (SECURITY T8/§4); all of it has a baked-in default so\n// the server reaches the live store with ZERO env (d2 / P4.5-7b):\n// - BT_STORE_URL the Medusa Store API base URL (default: prod; override for dev)\n// - BT_PUBLISHABLE_KEY the publishable API key — PUBLIC by design (default: prod; T6)\n// - BT_TOKEN the customer JWT (OPTIONAL; from the OS keychain / a configured env)\n// - BT_REGION an ISO-2 country code to resolve pricing (OPTIONAL; never hardcoded)\n//\n// The JWT is read from the env the host MCP config injects (which a keychain helper can populate)\n// — it is NEVER read from the chat (T8). A secret key is rejected by the StoreClient (T6). On\n// stdio, NOTHING is written to stdout except MCP protocol frames — diagnostics go to stderr.\n\nimport { StoreClient } from \"@binaryandthread/store-client\"\nimport { redact } from \"@binaryandthread/store-client\"\nimport { startServer } from \"./server\"\nimport { makeTools } from \"./tools\"\nimport { resolveServerConfig } from \"./config\"\n\nasync function main(): Promise<void> {\n const { baseUrl, publishableKey, jwt, region } = resolveServerConfig()\n\n const client = new StoreClient({\n baseUrl,\n publishableKey,\n jwt,\n // All client logs route to stderr through redact() — stdout is reserved for the transport,\n // and no token/PII ever reaches a log line (G10).\n logger: {\n info: (...a) => process.stderr.write(JSON.stringify(redact(a)) + \"\\n\"),\n warn: (...a) => process.stderr.write(JSON.stringify(redact(a)) + \"\\n\"),\n error: (...a) => process.stderr.write(JSON.stringify(redact(a)) + \"\\n\"),\n },\n })\n\n const tools = makeTools(client, { region })\n await startServer(tools)\n process.stderr.write(`bt-mcp: ready on stdio — ${tools.length} tools registered.\\n`)\n}\n\nmain().catch((e) => {\n // Redact before printing — never leak a token in a crash trace.\n process.stderr.write(`bt-mcp: fatal: ${JSON.stringify(redact(String(e?.message ?? e)))}\\n`)\n process.exit(1)\n})\n"],"mappings":"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","names":["out","stage"]}
|
|
1
|
+
{"version":3,"sources":["../../store-client/src/redact.ts","../../store-client/src/client.ts","../../store-client/src/defaults.ts","../../store-client/src/host-allowlist.ts","../../store-client/src/colour.ts","../../store-client/src/product.ts","../../store-client/src/tax.ts","../src/server.ts","../src/version.ts","../src/onboarding.ts","../src/tools.ts","../src/sanitize.ts","../src/images.ts","../src/eggs.ts","../src/config.ts","../src/bin.ts"],"sourcesContent":["// Secret/PII redaction for logs and error envelopes (SECURITY G10).\n//\n// The single rule for the whole channel: Authorization headers, tokens, and full\n// emails NEVER reach a log line, an error message, or an MCP/CLI transcript. This\n// function is applied by the StoreClient's logger and error mapper before anything\n// is emitted. It is consumed by BOTH the CLI and (Wave 4) the MCP — one redactor.\n\nconst SENSITIVE_KEYS = new Set([\n \"authorization\",\n \"token\",\n \"access_token\",\n \"refresh_token\",\n \"jwt\",\n \"idempotency-key\",\n \"x-publishable-api-key\",\n \"password\",\n \"secret\",\n \"cookie\",\n \"set-cookie\",\n])\n\nconst MASK = \"[REDACTED]\"\n\n// Bearer-token-in-a-string (e.g. \"Authorization: Bearer abc.def.ghi\" or a stray \"Bearer xyz\").\nconst BEARER_RE = /Bearer\\s+[A-Za-z0-9._\\-]+/gi\n// A plausible email anywhere in a string.\nconst EMAIL_RE = /([^\\s@]+)@([^\\s@]+\\.[^\\s@]+)/g\n\n/** Mask an email to `d***@x` form (first char + stars, host first char). */\nexport function maskEmail(email: string): string {\n const at = email.indexOf(\"@\")\n if (at <= 0) {\n return email\n }\n const local = email.slice(0, at)\n const host = email.slice(at + 1)\n const localMasked = local[0] + \"*\".repeat(Math.max(2, local.length - 1))\n const hostMasked = host.length > 0 ? host[0] : \"x\"\n return `${localMasked}@${hostMasked}`\n}\n\nfunction redactString(s: string): string {\n return s.replace(BEARER_RE, \"Bearer [REDACTED]\").replace(EMAIL_RE, (_m, _l, _h) =>\n maskEmail(`${_l}@${_h}`)\n )\n}\n\n/**\n * Deep-redact an object/array/string. Returns a structurally-similar value with\n * sensitive keys masked, Bearer tokens stripped, and emails masked. Never throws.\n *\n * `_seen` memoises input-object → its REDACTED output. This does double duty: it makes the\n * function cycle-safe (a revisited object resolves to its own — possibly still-filling — output\n * container) AND ensures a REPEATED reference is redacted every time. The previous WeakSet guard\n * returned the *original* object on revisit, leaking any token/email reachable via a 2nd path (G10).\n * The output container is registered BEFORE recursing so a cycle points at the redacted copy.\n */\nexport function redact<T>(input: T, _seen?: Map<object, unknown>): T {\n if (input == null) {\n return input\n }\n if (typeof input === \"string\") {\n return redactString(input) as unknown as T\n }\n if (typeof input !== \"object\") {\n return input\n }\n\n const seen = _seen ?? new Map<object, unknown>()\n const existing = seen.get(input as object)\n if (existing !== undefined) {\n return existing as T\n }\n\n if (Array.isArray(input)) {\n const out: unknown[] = []\n seen.set(input as object, out) // register before recursing (cycle-safe)\n for (const v of input) {\n out.push(redact(v, seen))\n }\n return out as unknown as T\n }\n\n const out: Record<string, unknown> = {}\n seen.set(input as object, out) // register before recursing (cycle-safe)\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key.toLowerCase())) {\n out[key] = MASK\n } else if (key.toLowerCase() === \"email\" && typeof value === \"string\") {\n out[key] = maskEmail(value)\n } else {\n out[key] = redact(value, seen)\n }\n }\n return out as unknown as T\n}\n","// StoreClient — the ONE shared HTTP client over the B&T Medusa Store API.\n//\n// Design (ROBUSTNESS §0): the clients are DUMB. Every dangerous property lives in the\n// backend. This module is the single place the channel's HTTP-client invariants live so\n// they are written once and contract-tested once (the CLI and the Wave-4 MCP both use it):\n//\n// - publishable key on EVERY request (public by design, T6); customer JWT when present\n// - Idempotency-Key (crypto.randomUUID) on EVERY mutation, STABLE across a retry — honoured by\n// the checkout MINT (per-cart session reuse); the core cart/line-item routes do NOT yet honour\n// it, so that guarantee needs a backend idempotency middleware (BACKLOG) (§2)\n// - retry 5xx + network errors with jittered backoff; NEVER retry a 4xx (F-A1/F-I2)\n// - region resolved before pricing — never a bare currency literal (F-A2/INV-11)\n// - 404/409/cart.completed on a cart mutation → RecoverableCartError (CLI makes a fresh cart) (F-B2)\n// - all logs + error envelopes pass through redact() — no token/email ever leaks (G10)\n// - NO secret key is ever stored or sent — publishable key only (T6)\n\nimport { redact } from \"./redact\"\nimport type {\n CartLike,\n ClientLogger,\n DropRow,\n HeadlessCheckout,\n OrderSummary,\n PollResult,\n ProductLike,\n RegionLike,\n RegionResolution,\n ReportResult,\n RequestLinkResult,\n RetryPolicy,\n ShippingOptionLike,\n StoreClientOptions,\n VerifyLinkResult,\n WhoAmI,\n} from \"./types\"\n\nconst DEFAULT_RETRY: RetryPolicy = {\n attempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 2000,\n}\n\n/** Cap on how long we'll honor a server's Retry-After, so a hostile/huge value can't hang us. */\nconst RETRY_AFTER_MAX_MS = 10_000\n\n/**\n * Per-attempt request timeout. A black-holed connection (socket accepted, no response) would\n * otherwise never settle, hanging every command/tool AND defeating the checkout poll's 20-min\n * deadline (F-I3) — the deadline can't be re-checked while stuck awaiting a dead fetch. 15s is\n * generous for the Store API while still failing closed on a hang.\n */\nconst DEFAULT_TIMEOUT_MS = 15_000\n\n/**\n * Extra fields to request on product reads. `+metadata` ADDS metadata to Medusa's default field set\n * (it doesn't replace it — variants + calculated_price still come through). metadata is where lore /\n * lore_sources / per-product spec fields live, and the Store API omits it unless asked. The `+` is\n * URL-encoded to %2B by URLSearchParams, which Medusa decodes back to `+metadata`.\n */\nconst PRODUCT_FIELDS = \"+metadata\"\n\n/**\n * Parse an HTTP Retry-After header → ms, or null if absent/unparseable. Accepts both forms:\n * delta-seconds (\"2\") and an HTTP-date. Capped at RETRY_AFTER_MAX_MS (d12).\n */\nfunction parseRetryAfterMs(res: Response): number | null {\n let raw: string | null = null\n try {\n raw = res.headers?.get?.(\"retry-after\") ?? null\n } catch {\n return null\n }\n if (!raw) {\n return null\n }\n const secs = Number(raw)\n let ms: number\n if (Number.isFinite(secs)) {\n ms = secs * 1000\n } else {\n const dateMs = Date.parse(raw)\n if (!Number.isFinite(dateMs)) {\n return null\n }\n ms = dateMs - Date.now()\n }\n return Math.min(Math.max(0, ms), RETRY_AFTER_MAX_MS)\n}\n\n/** A cart error the CLI maps to \"discard local cart_id, create a fresh cart, re-add\" (F-B2). */\nexport class RecoverableCartError extends Error {\n readonly recoverable = true as const\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = \"RecoverableCartError\"\n this.status = status\n }\n}\n\n/** A non-recoverable API error. Its message/envelope is already redacted (G10). */\nexport class StoreApiError extends Error {\n readonly status: number\n readonly path: string\n constructor(message: string, status: number, path: string) {\n super(message)\n this.name = \"StoreApiError\"\n this.status = status\n this.path = path\n }\n}\n\ntype HttpMethod = \"GET\" | \"POST\" | \"DELETE\"\n\ninterface RequestOpts {\n body?: unknown\n /** Mutations carry an Idempotency-Key. */\n idempotent?: boolean\n /** Treat a 404/409/cart.completed as a RecoverableCartError (cart mutations). */\n cartRecovery?: boolean\n query?: Record<string, string | number | undefined>\n /**\n * Statuses for which a 4xx is EXPECTED, not an error — the client_error envelope is NOT\n * logged (BUG 3: a guest whoami 401 is the normal unauthenticated path, not noise to dump).\n * The error is still thrown so the caller can branch on it.\n */\n expected4xx?: number[]\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/** crypto.randomUUID with a defensive fallback (older runtimes). */\nfunction newUuid(): string {\n const g = globalThis as unknown as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) {\n return g.crypto.randomUUID()\n }\n // RFC4122-ish fallback — never the happy path, just defensive.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === \"x\" ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nexport class StoreClient {\n private readonly baseUrl: string\n private readonly publishableKey: string\n private readonly jwt?: string\n private readonly fetchImpl: typeof fetch\n private readonly logger: ClientLogger\n private readonly retry: RetryPolicy\n private readonly sleepImpl: (ms: number) => Promise<void>\n private readonly timeoutMs: number\n\n /** Cached resolved regions (read-only catalog optimisation). */\n private regionsCache: RegionLike[] | null = null\n\n constructor(opts: StoreClientOptions) {\n if (!opts.baseUrl) {\n throw new Error(\"StoreClient: baseUrl is required\")\n }\n if (!opts.publishableKey) {\n throw new Error(\"StoreClient: publishableKey is required\")\n }\n // Guard against accidentally shipping a secret key (T6 — defensive).\n if (/^sk_/.test(opts.publishableKey)) {\n throw new Error(\"StoreClient: refusing a secret key — the client carries only the publishable key (T6)\")\n }\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\")\n this.publishableKey = opts.publishableKey\n this.jwt = opts.jwt\n this.fetchImpl = opts.fetchImpl ?? (globalThis.fetch as typeof fetch)\n this.logger = opts.logger ?? console\n this.retry = { ...DEFAULT_RETRY, ...(opts.retry ?? {}) }\n this.sleepImpl = opts.sleepImpl ?? sleep\n this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n }\n\n // ----- the request engine --------------------------------------------------\n\n private buildUrl(path: string, query?: RequestOpts[\"query\"]): string {\n const url = new URL(this.baseUrl + path)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined && v !== null && v !== \"\") {\n url.searchParams.set(k, String(v))\n }\n }\n }\n return url.toString()\n }\n\n private baseHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"x-publishable-api-key\": this.publishableKey,\n }\n if (this.jwt) {\n headers[\"Authorization\"] = `Bearer ${this.jwt}`\n }\n return headers\n }\n\n private backoffMs(attemptIndex: number): number {\n // exponential base with full jitter, capped.\n const exp = this.retry.baseDelayMs * 2 ** attemptIndex\n const capped = Math.min(exp, this.retry.maxDelayMs)\n return Math.floor(Math.random() * (capped + 1))\n }\n\n private async request<T>(method: HttpMethod, path: string, opts: RequestOpts = {}): Promise<T> {\n const headers = this.baseHeaders()\n // Generate the idempotency key ONCE and reuse it across retries (ROBUSTNESS §2). This only\n // prevents a duplicate when the SERVER honours Idempotency-Key: the checkout MINT does (it\n // reuses the open per-cart session), but the core Medusa cart/line-item routes do NOT yet — so a\n // retried createCart/addLineItem after a committed-but-lost response can still double-apply. The\n // real fix is a backend idempotency middleware (BACKLOG); until then a double-add is at least\n // VISIBLE (the CLI review + MCP checkout total show the real cart before any charge).\n if (opts.idempotent) {\n headers[\"Idempotency-Key\"] = newUuid()\n }\n const url = this.buildUrl(path, opts.query)\n const init: RequestInit = {\n method,\n headers,\n ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),\n }\n\n let lastErr: unknown\n for (let attempt = 0; attempt < this.retry.attempts; attempt++) {\n // A server-dictated delay for THIS attempt's backoff (429 Retry-After); null → jittered backoff.\n let retryAfterMs: number | null = null\n // Bound THIS attempt: a hung connection is aborted after timeoutMs. The AbortError lands in\n // the catch below and is treated as a retryable network error (so the backoff + retry, and\n // ultimately the exhausted-retries StoreApiError, all apply). Fresh controller per attempt.\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n try {\n const res = await this.fetchImpl(url as unknown as string, { ...init, signal: controller.signal })\n if (res.ok) {\n // 204 / empty body tolerance\n const text = await res.text()\n return (text ? JSON.parse(text) : {}) as T\n }\n\n // Read the error body defensively (may not be JSON).\n let parsed: Record<string, unknown> = {}\n try {\n const t = await res.text()\n parsed = t ? (JSON.parse(t) as Record<string, unknown>) : {}\n } catch {\n parsed = {}\n }\n\n // Cart recovery: a stale/completed cart on a cart mutation (F-B2).\n if (\n opts.cartRecovery &&\n (res.status === 404 ||\n res.status === 409 ||\n String(parsed.type ?? \"\").includes(\"cart.completed\") ||\n /cart.*complete/i.test(String(parsed.message ?? \"\")))\n ) {\n throw new RecoverableCartError(\n `cart is stale or completed (HTTP ${res.status})`,\n res.status\n )\n }\n\n // 429 Too Many Requests — RETRYABLE, honoring Retry-After (d12). Must be handled BEFORE\n // the generic 4xx-no-retry rule below, or a rate limit becomes a hard failure.\n if (res.status === 429) {\n retryAfterMs = parseRetryAfterMs(res)\n lastErr = new StoreApiError(`rate limited (HTTP 429)`, 429, path)\n this.logWarn(\"store.request.rate_limited\", {\n path,\n attempt: attempt + 1,\n retryAfterMs,\n })\n } else if (res.status >= 400 && res.status < 500) {\n // other 4xx is a client error — do NOT retry.\n // Suppress the envelope for EXPECTED 4xx (e.g. a guest whoami 401 — BUG 3): it's a\n // normal branch the caller handles, not noise to dump to the user's terminal.\n if (!(opts.expected4xx ?? []).includes(res.status)) {\n this.logError(\"store.request.client_error\", { path, status: res.status, body: parsed })\n }\n throw new StoreApiError(\n `request failed (HTTP ${res.status})`,\n res.status,\n path\n )\n } else {\n // 5xx — retryable. Fall through to the retry loop.\n lastErr = new StoreApiError(`upstream error (HTTP ${res.status})`, res.status, path)\n this.logWarn(\"store.request.retryable\", { path, status: res.status, attempt: attempt + 1 })\n }\n } catch (e) {\n // RecoverableCartError / StoreApiError(4xx) must propagate immediately.\n if (e instanceof RecoverableCartError) {\n throw e\n }\n if (e instanceof StoreApiError && e.status >= 400 && e.status < 500) {\n throw e\n }\n // Network error, an aborted (timed-out) request, or a 5xx StoreApiError → retryable.\n lastErr = e\n this.logWarn(\"store.request.network_retry\", { path, attempt: attempt + 1 })\n } finally {\n // Always clear the per-attempt timeout — on success-return, on a rethrow, or before a retry.\n clearTimeout(timer)\n }\n\n // Backoff before the next attempt (skip after the final attempt). A 429 Retry-After, when\n // present, dictates the delay; otherwise jittered exponential backoff (d12).\n if (attempt < this.retry.attempts - 1) {\n await this.sleepImpl(retryAfterMs ?? this.backoffMs(attempt))\n }\n }\n\n // Exhausted retries. The thrown envelope is already free of secrets (we only ever\n // log/throw redacted, derived data — never the request headers/body).\n this.logError(\"store.request.exhausted\", { path, attempts: this.retry.attempts })\n if (lastErr instanceof StoreApiError) {\n throw lastErr\n }\n throw new StoreApiError(\n `request failed after ${this.retry.attempts} attempts: ${path}`,\n 0,\n path\n )\n }\n\n private logWarn(event: string, ctx: Record<string, unknown>): void {\n this.logger.warn(event, redact(ctx))\n }\n\n private logError(event: string, ctx: Record<string, unknown>): void {\n this.logger.error(event, redact(ctx))\n }\n\n // ----- regions (F-A2 / INV-11) ---------------------------------------------\n\n async getRegions(): Promise<RegionLike[]> {\n const data = await this.request<{ regions: RegionLike[] }>(\"GET\", \"/store/regions\")\n this.regionsCache = data.regions ?? []\n return this.regionsCache\n }\n\n /**\n * Resolve a region by country code, WITH provenance (matched / home / requested), so callers on\n * the money path can fail closed on an unmatched explicit country instead of silently pricing in\n * the wrong currency (F-A2). Region #1 is the launch/home default — SEK today, derived, never a\n * hardcoded literal.\n *\n * Semantics:\n * - country given AND a region ships there → { matched: true, home: false }.\n * - country given, none ships there → { matched: false, home: false } (money surfaces MUST\n * refuse; discovery may show the home region + a warning). We still return a region so a\n * discovery command can render *something*, but `matched:false` is the signal to fail closed.\n * - no country given → { matched: false, home: true } (the legitimate\n * launch default — not a \"wrong\" currency).\n */\n async resolveRegionResult(countryCode?: string): Promise<RegionResolution> {\n const regions = this.regionsCache ?? (await this.getRegions())\n if (regions.length === 0) {\n // Fail closed: refuse to price without a resolved region (F-A2).\n throw new Error(\"no regions configured — cannot resolve a region for pricing\")\n }\n const home = regions[0]! // region #1 — the launch region (SEK today, derived not hardcoded)\n if (countryCode) {\n const cc = countryCode.toLowerCase()\n const match = regions.find((r) =>\n (r.countries ?? []).some((c) => (c.iso_2 ?? \"\").toLowerCase() === cc)\n )\n if (match) {\n return { region: match, matched: true, requested: cc, home: false }\n }\n // A country code was given but no region ships there. Warn (stderr, redacted) so the\n // mis-resolution is observable, and report matched:false so money surfaces fail closed.\n this.logWarn(\"store.region.unmatched\", { countryCode: cc, fallback: home.id })\n return { region: home, matched: false, requested: cc, home: false }\n }\n return { region: home, matched: false, requested: undefined, home: true }\n }\n\n /**\n * Resolve a region by country code, falling back to region #1. A thin wrapper over\n * resolveRegionResult for callers that only need the region (back-compat).\n * SEK is region #1 by configuration — NEVER a hardcoded literal.\n */\n async resolveRegion(countryCode?: string): Promise<RegionLike> {\n return (await this.resolveRegionResult(countryCode)).region\n }\n\n // ----- products (region resolved BEFORE pricing) ---------------------------\n\n async listProducts(opts?: { region?: RegionLike; countryCode?: string }): Promise<ProductLike[]> {\n const region = opts?.region ?? (await this.resolveRegion(opts?.countryCode))\n // Paginate (L12): a single limit:100 call silently drops the 101st product. Page through until a\n // short page, with a hard cap so a misbehaving API can never spin this unbounded.\n const PAGE = 100\n const HARD_CAP = 1000\n const all: ProductLike[] = []\n for (let offset = 0; offset < HARD_CAP; offset += PAGE) {\n const data = await this.request<{ products: ProductLike[] }>(\"GET\", \"/store/products\", {\n // +metadata: Medusa omits product.metadata by default, and that's where lore/lore_sources\n // (and any per-product spec fields) live — without this the CLI/MCP never see them.\n query: { region_id: region.id, limit: PAGE, offset, fields: PRODUCT_FIELDS },\n })\n const page = data.products ?? []\n all.push(...page)\n if (page.length < PAGE) {\n break\n }\n }\n return all\n }\n\n async getProduct(handle: string, opts?: { region?: RegionLike; countryCode?: string }): Promise<ProductLike | null> {\n const region = opts?.region ?? (await this.resolveRegion(opts?.countryCode))\n const data = await this.request<{ products: ProductLike[] }>(\"GET\", \"/store/products\", {\n query: { handle, region_id: region.id, limit: 1, fields: PRODUCT_FIELDS },\n })\n return (data.products ?? [])[0] ?? null\n }\n\n // ----- cart (mutations carry Idempotency-Key) ------------------------------\n\n async createCart(region: RegionLike): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\"POST\", \"/store/carts\", {\n idempotent: true,\n body: { region_id: region.id },\n })\n return data.cart\n }\n\n async addLineItem(cartId: string, variantId: string, quantity: number): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\n \"POST\",\n `/store/carts/${encodeURIComponent(cartId)}/line-items`,\n {\n idempotent: true,\n cartRecovery: true,\n body: { variant_id: variantId, quantity },\n }\n )\n return data.cart\n }\n\n async getCart(cartId: string): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\n \"GET\",\n `/store/carts/${encodeURIComponent(cartId)}`,\n { cartRecovery: true }\n )\n return data.cart\n }\n\n /** Remove a staged line item (bt unstage). Returns the updated cart (Medusa's `parent`). */\n async removeLineItem(cartId: string, itemId: string): Promise<CartLike> {\n const data = await this.request<{ parent?: CartLike; cart?: CartLike }>(\n \"DELETE\",\n `/store/carts/${encodeURIComponent(cartId)}/line-items/${encodeURIComponent(itemId)}`,\n { idempotent: true, cartRecovery: true }\n )\n return (data.parent ?? data.cart) as CartLike\n }\n\n /**\n * Ensure the cart has a shipping method before checkout (BUG 1 — the headless\n * /store/headless/checkout route guards on it, and neither client ever set one).\n *\n * - Idempotent: if the cart already carries a shipping method, no-op (no POST).\n * - Lists options via the STANDARD store route GET /store/shipping-options?cart_id=…\n * (NOT a /headless route — standard routes are always available, no kill-switch).\n * - Picks deterministically: exactly one → use it; several → the cheapest by amount.\n * - The SE launch option is region-scoped (price_type:\"flat\"), so listing by cart_id\n * returns it without a shipping address being set first (confirmed: seed-shipping.ts).\n *\n * TODO(04.5): buyer-choice / intl multiple shipping options — today we auto-pick the\n * cheapest; an explicit buyer chooser belongs to the Phase-6 international pass.\n */\n async ensureCartShippingMethod(cartId: string): Promise<CartLike> {\n // 1. read the authoritative cart; a stale/completed cart → RecoverableCartError (F-B2).\n const cart = await this.getCart(cartId)\n if (cart.shipping_methods && cart.shipping_methods.length > 0) {\n // idempotent: already satisfied.\n return cart\n }\n\n // 2. list options via the STANDARD store route (always available, no kill-switch).\n const data = await this.request<{ shipping_options: ShippingOptionLike[] }>(\n \"GET\",\n \"/store/shipping-options\",\n { query: { cart_id: cartId } }\n )\n const options = data.shipping_options ?? []\n if (options.length === 0) {\n // fail closed: never mint a checkout we know the backend will reject (BUG 1).\n throw new Error(\n \"no shipping option is available for this cart — cannot attach a shipping method\"\n )\n }\n\n // 3. pick deterministically: one → it; several → the cheapest by amount.\n const chosen =\n options.length === 1\n ? options[0]!\n : [...options].sort(\n (a, b) => (a.amount ?? Number.POSITIVE_INFINITY) - (b.amount ?? Number.POSITIVE_INFINITY)\n )[0]!\n\n // 4. attach it — a mutation, so it carries an Idempotency-Key + cart-recovery.\n const res = await this.request<{ cart: CartLike }>(\n \"POST\",\n `/store/carts/${encodeURIComponent(cartId)}/shipping-methods`,\n {\n idempotent: true,\n cartRecovery: true,\n body: { option_id: chosen.id },\n }\n )\n return res.cart\n }\n\n // ----- headless contract (04.5-01) -----------------------------------------\n\n // `channel` is REQUIRED (L11): a default silently mis-attributes analytics for a future caller that\n // forgets it. Both current callers (cli, mcp) pass it explicitly.\n async mintCheckout(cartId: string, email: string | undefined, channel: string): Promise<HeadlessCheckout> {\n return this.request<HeadlessCheckout>(\"POST\", \"/store/headless/checkout\", {\n idempotent: true,\n body: { cart_id: cartId, email, channel },\n })\n }\n\n async poll(sessionId: string): Promise<PollResult> {\n return this.request<PollResult>(\n \"GET\",\n `/store/headless/poll/${encodeURIComponent(sessionId)}`\n )\n }\n\n /** JWT-bound: returns the authenticated customer's own profile (G2). */\n async whoami(): Promise<WhoAmI> {\n // A guest (no/expired JWT) gets a 401/403 — that's the EXPECTED unauthenticated branch the\n // CLI turns into the friendly \"browsing as a guest\" line, NOT an error to dump (BUG 3).\n return this.request<WhoAmI>(\"GET\", \"/store/headless/whoami\", { expected4xx: [401, 403] })\n }\n\n // ----- magic-link auth (04.5-05) -------------------------------------------\n // Passwordless login — magic-link is the only grant (Medusa has no OAuth pairing-grant, G1).\n // request-link emails a single-use link; verify exchanges the link's token for a customer-scoped\n // JWT. The JWT is stored in the OS keychain by the CLI — never here, never in plaintext config (G7/G8).\n\n /** Start a magic-link login. Returns a UNIFORM ack regardless of whether the email exists (G12). */\n async requestMagicLink(email: string): Promise<RequestLinkResult> {\n return this.request<RequestLinkResult>(\"POST\", \"/store/headless/auth/request-link\", {\n idempotent: true,\n body: { email },\n })\n }\n\n /** Exchange a magic-link token for a customer-scoped JWT (the customer is materialized server-side). */\n async verifyMagicLink(token: string): Promise<VerifyLinkResult> {\n return this.request<VerifyLinkResult>(\"POST\", \"/store/headless/auth/verify\", {\n idempotent: true,\n body: { token },\n })\n }\n\n async listDrops(): Promise<DropRow[]> {\n const data = await this.request<{ drops: DropRow[]; count: number }>(\n \"GET\",\n \"/store/headless/drops\"\n )\n return data.drops ?? []\n }\n\n /**\n * File a developer bug report (d14 — `bt report` / MCP report_issue). Returns a correlation\n * reference. Additive: no money/cart path. The backend redacts authoritatively; callers SHOULD\n * pass an already-redacted context (the doctor snapshot). A mutation → carries an Idempotency-Key\n * so a retry can't email the report twice.\n */\n async report(message: string, context?: Record<string, unknown>): Promise<ReportResult> {\n return this.request<ReportResult>(\"POST\", \"/store/headless/report\", {\n idempotent: true,\n body: { message, ...(context !== undefined ? { context } : {}) },\n })\n }\n\n /**\n * The authenticated customer's order history (d13 — `bt log`). JWT-bound; the backend derives the\n * customer from the bearer token and returns MINIMAL fields (status/timeline only — never\n * email/address, F-H3). A guest (no/expired JWT) gets a 401 — the EXPECTED unauthenticated branch\n * we turn into the honest empty history (`[]`), NOT an error to dump (BUG 3).\n */\n async listOrders(): Promise<OrderSummary[]> {\n try {\n const data = await this.request<{ orders: OrderSummary[] }>(\"GET\", \"/store/headless/orders\", {\n expected4xx: [401, 403],\n })\n return data.orders ?? []\n } catch (e) {\n if (e instanceof StoreApiError && (e.status === 401 || e.status === 403)) {\n return []\n }\n throw e\n }\n }\n\n /**\n * Guest order lookup (AUD-8) — POST /store/order-lookup with the order id (display id OR internal\n * id) + the EXACT email in the BODY (never the query string, so the email is not access-logged).\n * The backend returns 404 on any mismatch (no enumeration) → the client surfaces null. Rate-limited\n * server-side; returns minimal status only, never PII (F-H1 / F-H3).\n */\n async getOrder(idOrDisplayId: string | number, email: string): Promise<unknown | null> {\n try {\n return await this.request<unknown>(\"POST\", \"/store/order-lookup\", {\n body: { id: String(idOrDisplayId), email },\n })\n } catch (e) {\n if (e instanceof StoreApiError && e.status === 404) {\n return null\n }\n throw e\n }\n }\n}\n","// Baked-in PUBLIC defaults for the Binary & Thread production channel.\n//\n// These let `npx @binaryandthread/cli` and the MCP server reach the live store with ZERO\n// configuration — the point of the soft-launch tarball (P4.5-7b) and decision #79 (d2).\n// store-client is bundled into both the CLI and the MCP server (tsup `noExternal`), so this\n// is the single source of truth for \"which store\" both clients talk to by default.\n//\n// SAFETY (CLAUDE.md rule 1 / SECURITY T6): the publishable key is PUBLIC BY DESIGN. It is the\n// SAME key embedded in the public storefront's client bundle (binaryandthread.com) and carries\n// no privilege beyond read + cart over the Store API — every dangerous operation lives behind\n// the backend. It is therefore NOT a secret; rule 1 governs secrets, and this is deliberately\n// published. Any env var (BT_API_URL / BT_STORE_URL / BT_PUBLISHABLE_KEY / MEDUSA_*) OVERRIDES\n// these for local dev or staging.\n\n/** The Binary & Thread production Medusa Store API base URL. */\nexport const BT_DEFAULT_STORE_URL = \"https://backend-production-511f5.up.railway.app\"\n\n// The PUBLIC Medusa publishable key (read + cart only; safe to ship — T6). Identical to the key\n// served in the public storefront bundle. NOT a secret (CLAUDE.md rule 1); the inline allow keeps\n// the gitleaks pre-commit hook from flagging a deliberately-published value.\nexport const BT_DEFAULT_PUBLISHABLE_KEY = \"pk_0d7d706860568a0414dff0436aadb9713a2609de3a1b153ef2a6f9694d23f907\" // gitleaks:allow\n","// Image-host allowlist — the canonical source for the WHOLE channel (CLAUDE.md rule 6 / SECURITY G9).\n//\n// We fetch product images ONLY from our own Vercel Blob store. Both the bt CLI (its terminal\n// image ladder) and the Wave-4 MCP (its server-side ImageContent fetch) gate every outbound\n// image fetch on this allowlist. It lives HERE in the shared client — not in the CLI — so the\n// MCP does not take a runtime dependency on the CLI package; the CLI re-exports it from here.\n//\n// HISTORY: a bare `endsWith(allowed)` suffix match was a CRITICAL allowlist-bypass bug fixed in\n// Wave 2 (it would admit `evilblob.vercel-storage.com`). The dot-delimited-subdomain check below\n// is load-bearing security — `host === allowed || host.endsWith(\".\" + allowed)`, never a bare\n// suffix. Do NOT \"simplify\" it.\n\n/**\n * Allowlisted image hosts. We fetch product images ONLY from our own Vercel Blob store\n * (public). NEVER a public temp host (uguu/0x0/catbox/transfer.sh) and NEVER an arbitrary\n * URL — that would be SSRF + a brand-IP leak (CLAUDE.md rule 6).\n */\nexport const BLOB_HOST_ALLOWLIST: readonly string[] = [\n \"blob.vercel-storage.com\", // matches the exact host + *.blob.vercel-storage.com (dot-delimited subdomains only)\n]\n\n/**\n * True if the URL's host is on the Blob allowlist.\n *\n * Exact host OR a true dot-delimited subdomain. NOT a bare suffix: a bare\n * `endsWith(allowed)` would admit `evilblob.vercel-storage.com` (allowlist bypass →\n * SSRF / brand-IP leak, CLAUDE.md rule 6). The dot guard is load-bearing.\n */\nexport function isAllowedImageHost(url: string): boolean {\n let host: string\n try {\n host = new URL(url).hostname.toLowerCase()\n } catch {\n return false\n }\n return BLOB_HOST_ALLOWLIST.some(\n (allowed) => host === allowed || host.endsWith(\".\" + allowed)\n )\n}\n","// Colour resolution — DERIVED from the variant matrix, never hardcoded (CAT-3 forward-compat).\n//\n// The dual-colour catalog (memory: dual-colour-catalog) gives each design ONE product with a\n// Colour × Size variant matrix. A variant's colour comes from, in order of trust:\n// 1. the variant option titled \"Colour\" (Model A: options {Size, Colour}, value title-cased)\n// 2. the variant title, format \"<size> / <Colour>\" (e.g. \"S / White\", \"M / Black\")\n// We NEVER hardcode white/black — future colours (CAT-3) fall out of the data for free.\n//\n// This lives in @binaryandthread/store-client (the canonical types home) so the CLI and the MCP\n// resolve colour the SAME way — written once, tested once.\n\nimport type { ProductLike, VariantLike, ImageLike } from \"./types\"\n\n/** Title-case a colour token for display (\"white\" → \"White\"); leaves multi-word reasonable. */\nexport function displayColour(colour: string): string {\n const c = colour.trim()\n if (!c) return c\n return c.charAt(0).toUpperCase() + c.slice(1).toLowerCase()\n}\n\n/**\n * The colour of a single variant, lowercased — or null if it carries no colour axis (a\n * legacy Size-only product). Reads the \"Colour\" option first, then parses the \"<size> / <Colour>\"\n * title. NEVER assumes a fixed palette.\n */\nexport function variantColour(v: VariantLike): string | null {\n // 1) the explicit Colour option (the authoritative source — memory: never parse the title).\n const opt = (v.options ?? []).find(\n (o) => (o.option?.title ?? \"\").toLowerCase() === \"colour\" || (o.option?.title ?? \"\").toLowerCase() === \"color\"\n )\n if (opt && typeof opt.value === \"string\" && opt.value.trim()) {\n return opt.value.trim().toLowerCase()\n }\n // 2) fall back to the title format \"<size> / <Colour>\".\n const title = (v.title ?? \"\").trim()\n if (title.includes(\"/\")) {\n const parts = title.split(\"/\").map((p) => p.trim())\n const tail = parts[parts.length - 1]\n if (tail) return tail.toLowerCase()\n }\n return null\n}\n\n/**\n * The DISTINCT colours offered by a product, in first-seen order, lowercased. Empty for a\n * legacy Size-only product (no Colour axis) — callers treat that as \"no colour to show\".\n */\nexport function productColours(p: ProductLike): string[] {\n const seen: string[] = []\n for (const v of p.variants ?? []) {\n const c = variantColour(v)\n if (c && !seen.includes(c)) {\n seen.push(c)\n }\n }\n return seen\n}\n\n/** The distinct sizes offered FOR a given colour (lowercased colour match), preserving order. */\nexport function sizesForColour(p: ProductLike, colour: string): string[] {\n const target = colour.trim().toLowerCase()\n const sizes: string[] = []\n for (const v of p.variants ?? []) {\n if (variantColour(v) !== target) continue\n const size = sizeOfVariant(v)\n if (size && !sizes.includes(size)) {\n sizes.push(size)\n }\n }\n return sizes\n}\n\n/** The size label of a variant — the Size option, else the title's leading \"<size>\" segment. */\nexport function sizeOfVariant(v: VariantLike): string | null {\n const opt = (v.options ?? []).find((o) => (o.option?.title ?? \"\").toLowerCase() === \"size\")\n if (opt && typeof opt.value === \"string\" && opt.value.trim()) {\n return opt.value.trim()\n }\n const title = (v.title ?? \"\").trim()\n if (title.includes(\"/\")) {\n const head = title.split(\"/\")[0]?.trim()\n if (head) return head\n }\n // No colour axis → the whole title IS the size (legacy Size-only product).\n return title || null\n}\n\n/**\n * Resolve a variant by size AND colour (the real buyability gap — bt stage / MCP add_to_cart).\n * - size + colour given → exact match on both (case-insensitive).\n * - size only → first variant of that size.\n * - colour only → first variant of that colour.\n * - neither → first variant.\n * Returns undefined when the requested size+colour combination does not exist (the caller MUST\n * surface a clear error — never silently substitute another colour the buyer didn't ask for).\n */\nexport function findVariant(\n p: ProductLike,\n opts: { size?: string; colour?: string } = {}\n): VariantLike | undefined {\n const variants = p.variants ?? []\n const size = opts.size?.trim().toLowerCase()\n const colour = opts.colour?.trim().toLowerCase()\n\n const sizeMatches = (v: VariantLike): boolean => {\n if (!size) return true\n return (sizeOfVariant(v) ?? \"\").toLowerCase() === size || (v.title ?? \"\").toLowerCase() === size\n }\n const colourMatches = (v: VariantLike): boolean => {\n if (!colour) return true\n return variantColour(v) === colour\n }\n\n if (size && colour) {\n // both specified → require BOTH; no fallback to a different colour (a wrong colour is worse\n // than a clear \"that combo isn't available\").\n return variants.find((v) => sizeMatches(v) && colourMatches(v))\n }\n if (colour && !size) {\n return variants.find((v) => colourMatches(v))\n }\n if (size && !colour) {\n // size specified → require it; NO fallback to variants[0] (L10) — a silently-wrong size is worse\n // than undefined, which lets the caller (stage.ts) surface a clear \"no matching variant\" error.\n return variants.find((v) => sizeMatches(v))\n }\n return variants[0]\n}\n\n/**\n * The image URL to render for a given colour. Resolves, in order:\n * 1. the matching variant's own images[] (most precise)\n * 2. a product image whose url/metadata names the colour\n * 3. null → the caller falls back to product.thumbnail + a \"black available\" note.\n * Never throws; a missing colour just yields null.\n */\nexport function imageForColour(p: ProductLike, colour: string): string | null {\n const target = colour.trim().toLowerCase()\n if (!target) return p.thumbnail ?? firstImageUrl(p.images) ?? null\n\n // 1) the colour's variant images.\n for (const v of p.variants ?? []) {\n if (variantColour(v) !== target) continue\n const url = firstImageUrl(v.images)\n if (url) return url\n }\n\n // 2) a product image that names the colour (url substring or metadata.colour tag).\n for (const img of p.images ?? []) {\n const tag = (img.metadata?.[\"colour\"] ?? img.metadata?.[\"color\"] ?? \"\")\n .toString()\n .toLowerCase()\n if (tag === target) {\n const u = (img.url ?? \"\").trim()\n if (u) return u\n }\n }\n for (const img of p.images ?? []) {\n const u = (img.url ?? \"\").trim()\n if (u && new RegExp(`(^|[^a-z])${escapeRegex(target)}([^a-z]|$)`, \"i\").test(u)) {\n return u\n }\n }\n return null\n}\n\nfunction firstImageUrl(images: Array<ImageLike> | null | undefined): string | null {\n for (const img of images ?? []) {\n const u = (img?.url ?? \"\").trim()\n if (u) return u\n }\n return null\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n","// Canonical product-content accessors — the ONE source for a piece's README, spec sheet, and lore,\n// shared by the CLI (`bt view` / `bt blame`), the MCP (`get_product`), and (aspirationally) the\n// storefront PDP. Keeping these here means the three channels can't drift on what a piece \"says\".\n//\n// The spec fields + fallbacks mirror the storefront's buildSpecs (apps/storefront .../templates).\n\nimport type { ProductLike } from \"./types\"\n\n/** One row of the spec sheet. */\nexport interface SpecEntry {\n key: string\n value: string\n}\n\n/** A lore link (a real source behind the pun/idea). */\nexport interface LoreSource {\n url: string\n label: string\n}\n\n/** A piece's lore: the story text + any sources. Empty text = no lore on record. */\nexport interface ProductLore {\n text: string\n sources: LoreSource[]\n}\n\n/** A non-empty string, or the fallback. */\nfunction str(v: unknown, fallback: string): string {\n return typeof v === \"string\" && v.length > 0 ? v : fallback\n}\n\n/** The piece's README — the human description shown on the PDP's README toggle. */\nexport function productReadme(p: ProductLike): string {\n return typeof p.description === \"string\" ? p.description : \"\"\n}\n\n/**\n * The spec sheet — the 6 canonical rows, in PDP order, with the same fallbacks as the storefront's\n * buildSpecs (the single source of truth for what a piece is made of / how it ships).\n */\nexport function productSpecs(p: ProductLike): SpecEntry[] {\n const md = (p.metadata ?? {}) as Record<string, unknown>\n return [\n { key: \"material\", value: str(md.material, \"100% cotton\") },\n { key: \"weight\", value: str(md.weight, \"180 g/m²\") },\n { key: \"fit\", value: str(md.fit, \"Unisex heavyweight crewneck\") },\n { key: \"print\", value: str(md.print, \"Front print\") },\n { key: \"delivery\", value: str(md.delivery, \"Ships in 2–5 business days + transit\") },\n { key: \"care\", value: str(md.care, \"Cold wash, line dry\") },\n ]\n}\n\n/** The piece's lore — the story behind the pun + any real sources. Empty text = none on record. */\nexport function productLore(p: ProductLike): ProductLore {\n const md = (p.metadata ?? {}) as Record<string, unknown>\n const text = typeof md.lore === \"string\" ? md.lore : \"\"\n const raw = Array.isArray(md.lore_sources) ? md.lore_sources : []\n const sources: LoreSource[] = raw\n .filter(\n (s): s is { url: string; label: string } =>\n !!s &&\n typeof s === \"object\" &&\n typeof (s as { url?: unknown }).url === \"string\" &&\n typeof (s as { label?: unknown }).label === \"string\"\n )\n .map((s) => ({ url: s.url, label: s.label }))\n return { text, sources }\n}\n","// Tax-inclusivity — the single source of truth for the \"incl. VAT\" price label across EVERY\n// channel (storefront, the bt CLI, the MCP server). Kept here (in the shared client) so the CLI\n// and MCP agree with each other and with the backend on how the flag is read.\n\nimport type { CartLike } from \"./types\"\n\n/**\n * Whether a cart's prices are VAT-inclusive (EU/SE) vs tax-exclusive (US).\n *\n * Medusa exposes tax-inclusivity PER LINE ITEM (and per shipping method), never on the cart root —\n * there is no `cart.is_tax_inclusive` in @medusajs/types 2.15.5. So we DERIVE it from the items.\n * It is uniform per region (every line shares the region's tax setting), so `.some` is exact and a\n * mixed cart cannot occur. A US (tax-exclusive) cart → false → NO \"incl. VAT\" label; an SE cart →\n * true → \" incl. VAT\". This is NEVER keyed off a currency or country literal (INV-11): the label\n * follows the market's own tax flag, so it stays correct as new regions come online.\n *\n * Fails closed: an empty/absent cart or items array → false (no phantom VAT label on nothing).\n */\nexport function isCartTaxInclusive(cart: Pick<CartLike, \"items\"> | null | undefined): boolean {\n return (cart?.items ?? []).some((i) => i.is_tax_inclusive === true)\n}\n","// The B&T MCP server — stdio transport, registering exactly the channel's deterministic tools.\n//\n// The server is a THIN registrar: each tool is a deterministic wrapper over the shared\n// StoreClient (no free-form execution, no admin op — SECURITY T10/G8). The tool DEFINITIONS\n// (names, input schemas, handlers) live in tools.ts; this module just builds the McpServer and\n// wires the stdio transport so a developer's AI agent can call them over stdin/stdout.\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\"\nimport type { ZodRawShape } from \"zod\"\nimport type { ImageBlock } from \"./images\"\nimport { VERSION } from \"./version\"\nimport { GETTING_STARTED, GETTING_STARTED_URI, buyPromptText } from \"./onboarding\"\n\n/** What a tool handler returns — MCP content blocks (image and/or text). */\nexport interface ToolResult {\n content: ImageBlock[]\n /** Set when the tool reports a user-facing error (e.g. a 404 lookup) — not a transport error. */\n isError?: boolean\n /**\n * Machine-readable result (ids/handles like cart_id/session_id/order_id) so an agent can chain\n * tools WITHOUT regexing the prose (d9). Already redacted (G10). Mirrors the human text block.\n */\n structuredContent?: Record<string, unknown>\n}\n\n/**\n * A deterministic tool definition. `inputSchema` is a Zod raw shape (the SDK turns it into a JSON\n * schema for the agent). The handler receives the parsed args and returns content blocks. Eggs\n * (VOICE §3.2 M2/M3) may live in the `description` text but NEVER alter behavior.\n */\nexport interface ToolDef<S extends ZodRawShape = ZodRawShape> {\n name: string\n description: string\n inputSchema: S\n handler: (args: Record<string, unknown>) => Promise<ToolResult>\n}\n\n// Advertised to the host in the initialize handshake. Version is single-sourced from version.ts\n// (mirrors package.json; version.test.ts locks them together) — no more silent drift.\nexport const SERVER_INFO = {\n name: \"binary-and-thread\",\n version: VERSION,\n} as const\n\n/**\n * Build an McpServer and register every tool. Returns the server plus the registered tool names\n * (so callers/tests can assert exactly the 10 tools are present). Pure — no transport, no I/O.\n */\nexport function buildServer(tools: ToolDef[]): {\n server: McpServer\n toolNames: string[]\n resourceNames: string[]\n promptNames: string[]\n} {\n const server = new McpServer(SERVER_INFO)\n const toolNames: string[] = []\n // The SDK's registerTool generic over a ZodRawShape input schema infers excessively deeply\n // (TS2589); we register through a structurally-typed view of the method to keep the call site\n // simple. The runtime contract is unchanged — name, { description, inputSchema }, handler.\n const register = server.registerTool.bind(server) as unknown as (\n name: string,\n config: { description: string; inputSchema: ZodRawShape },\n cb: (args: Record<string, unknown>) => Promise<{\n content: ImageBlock[]\n isError?: boolean\n structuredContent?: Record<string, unknown>\n }>\n ) => void\n for (const t of tools) {\n register(\n t.name,\n { description: t.description, inputSchema: t.inputSchema },\n // The SDK validates args against inputSchema before calling us. We hand the parsed args to\n // the deterministic handler and return its content blocks (+ optional structuredContent, d9).\n async (args: Record<string, unknown>) => {\n const res = await t.handler(args ?? {})\n return {\n content: res.content,\n ...(res.isError ? { isError: true } : {}),\n ...(res.structuredContent ? { structuredContent: res.structuredContent } : {}),\n }\n }\n )\n toolNames.push(t.name)\n }\n\n // Agent onboarding: a getting-started RESOURCE + a `buy` PROMPT (neither can touch money — they're\n // read-only orientation). Registered through structurally-typed views of the SDK methods to avoid\n // the deep generic inference the tool registration also side-steps.\n const registerResource = server.registerResource.bind(server) as unknown as (\n name: string,\n uri: string,\n metadata: { title?: string; description?: string; mimeType?: string },\n cb: (uri: URL) => { contents: Array<{ uri: string; text: string; mimeType?: string }> }\n ) => void\n registerResource(\n \"getting-started\",\n GETTING_STARTED_URI,\n { title: \"binary & thread — getting started\", description: \"the buy flow + the money boundary, for a cold agent\", mimeType: \"text/plain\" },\n () => ({ contents: [{ uri: GETTING_STARTED_URI, text: GETTING_STARTED, mimeType: \"text/plain\" }] })\n )\n const resourceNames = [\"getting-started\"]\n\n const registerPrompt = server.registerPrompt.bind(server) as unknown as (\n name: string,\n config: { title?: string; description?: string; argsSchema?: ZodRawShape },\n cb: (args: Record<string, unknown>) => { messages: Array<{ role: \"user\"; content: { type: \"text\"; text: string } }> }\n ) => void\n registerPrompt(\n \"buy\",\n { title: \"buy a piece\", description: \"walk through buying a piece (respecting the card-stays-with-the-human boundary)\" },\n (args) => ({\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\",\n text: buyPromptText(\n typeof args?.handle === \"string\" ? args.handle : undefined,\n typeof args?.region === \"string\" ? args.region : undefined\n ),\n },\n },\n ],\n })\n )\n const promptNames = [\"buy\"]\n\n return { server, toolNames, resourceNames, promptNames }\n}\n\n/** Build the server with the real tools and start it on stdio. Used by bin.ts. */\nexport async function startServer(tools: ToolDef[]): Promise<McpServer> {\n const { server } = buildServer(tools)\n const transport = new StdioServerTransport()\n await server.connect(transport)\n return server\n}\n","// The single source of truth for the MCP server version string.\n//\n// Mirrored into mcp/package.json's \"version\" field; version.test.ts asserts the two never drift.\n// We don't import package.json directly (JSON imports complicate the ESM dist build), so this\n// literal is the canonical value the server advertises to a host in the `initialize` handshake\n// (SERVER_INFO.version). Before this, SERVER_INFO hardcoded \"0.1.0\" and drifted from the published\n// package — a host inspecting the server saw a stale version, and bug reports mis-attributed it.\nexport const VERSION = \"0.2.0\"\n","// Agent onboarding surfaces — a getting-started RESOURCE + a `buy` PROMPT. The MCP parallel to the\n// CLI's provenance line + `bt init`: a cold agent shouldn't have to infer the flow (or the money\n// boundary) from ten tool descriptions. Deadpan-competent dev voice; brand-voice clean (never\n// \"tee\"/\"t-shirt\"/print-on-demand/Gelato — enforced by brand-voice.test.ts scanning src/).\n\nexport const GETTING_STARTED_URI = \"bt://getting-started\"\n\nexport const GETTING_STARTED = `binary & thread — buying from your agent.\n\nthe flow (each tool returns machine-readable structuredContent, so you can chain without regexing prose):\n 1. list_products [region] what's in stock, priced in the ship-to market's currency\n 2. get_product <handle> full detail + an authoritative variant_id + sizes + colours\n 3. add_to_cart … start or reuse a cart (returns a durable cart_id handle)\n 4. get_cart <cart_id> the authoritative cart — note: the total is PRE-shipping\n 5. checkout <cart_id> mint a pay link + the SHIPPING-INCLUSIVE total the buyer pays\n 6. check_payment <session_id> poll once for the result (call again if still pending)\n\nthe boundary (by design): payment + ship-to details are entered by the human on the storefront.\nthey never enter this chat, and you cannot complete a purchase yourself — you walk them to the door.\n\nregion + currency: prices and shipping follow the ship-to country. pass region (ISO-2, e.g. se / us /\nde) to list_products / get_product / add_to_cart, or the host can set BT_REGION. a country we don't\nship to is refused rather than priced in the wrong currency.`\n\n/** The `buy` prompt body — a templated walk-through of the flow + the boundary, optionally seeded\n * with a handle/region the user named. Returned as a single user-turn message. */\nexport function buyPromptText(handle?: string, region?: string): string {\n const target = handle ? `\"${handle}\"` : \"the piece the user wants\"\n const reg = region ? ` Price it for region \"${region}\".` : \"\"\n return `Help me buy ${target} from binary & thread through the MCP tools.${reg}\n\nSteps:\n1. If you don't have an exact variant_id, call get_product to get valid sizes/colours + the variant_id.\n2. add_to_cart with the variant_id (or handle + size + colour). Keep the returned cart_id.\n3. Show me the cart total, and remind me it excludes shipping until checkout.\n4. When I confirm, call checkout and give me the pay link + the shipping-inclusive total.\n5. Do NOT ask me for card or address details — I enter those in the browser. After I pay, use\n check_payment with the session_id to confirm the order.`\n}\n","// The 10 deterministic MCP tools over the SHARED StoreClient (SECURITY G8/T10; ROBUSTNESS §4.2/4.3).\n//\n// These are thin wrappers — no free-form execution, no admin op, no money self-completion. They\n// reuse the same idempotency / region-resolution / retry / redaction that the CLI uses (it all\n// lives inside the StoreClient — written once, contract-tested once).\n//\n// The hard invariants this file enforces (the MCP red-team gates):\n// - checkout (G8 confused-deputy / F-I4): returns {checkout_url, session_id} INSTANTLY. It NEVER\n// polls, NEVER opens a browser, NEVER sets a shipping address. Its result text tells the human\n// to open the link and explicitly says \"I can't complete this for you\". The human pays on the\n// storefront (Stripe Elements) — payment details NEVER enter the chat. check_payment polls; the\n// agent re-calls at its own cadence (async handle-ID).\n// (NB: there is deliberately NO payment-detail field anywhere in this package; the security\n// verify greps for those field tokens and must come back clean.)\n// - list views are TEXT-ONLY (no per-item images — token-bloat guard, ROBUSTNESS §4.3).\n// - every backend string is sanitize()'d (G8); every output object is redactForOutput()'d (G10).\n// - get_order_status returns status/timeline only — NO address (F-H3); id + exact-email, 404 on\n// miss (F-H1).\n\nimport { z } from \"zod\"\nimport {\n RecoverableCartError,\n StoreApiError,\n productColours,\n variantColour,\n sizesForColour,\n findVariant,\n productSpecs,\n productLore,\n productReadme,\n isCartTaxInclusive,\n} from \"@binaryandthread/store-client\"\nimport type {\n StoreClient,\n ProductLike,\n CartLike,\n RegionLike,\n} from \"@binaryandthread/store-client\"\nimport { sanitize, redactForOutput } from \"./sanitize\"\nimport { buildImageContent } from \"./images\"\nimport type { ImageBlock } from \"./images\"\nimport type { ToolDef, ToolResult } from \"./server\"\nimport { TEAPOT_TEXT, FORTUNES, fortune } from \"./eggs\"\n\nexport interface ToolsOpts {\n /** ISO-2 country code to resolve pricing (never a hardcoded currency literal). */\n region?: string\n}\n\n// ----- result helpers ---------------------------------------------------------\n\nfunction text(s: string): ToolResult {\n return { content: [{ type: \"text\", text: s }] }\n}\n\nfunction errorText(s: string): ToolResult {\n return { content: [{ type: \"text\", text: s }], isError: true }\n}\n\n/** A text block PLUS machine-readable structuredContent (ids/handles) for agent chaining (d9). */\nfunction result(textBody: string, structured: Record<string, unknown>): ToolResult {\n return {\n content: [{ type: \"text\", text: textBody }],\n structuredContent: redactForOutput(structured) as Record<string, unknown>,\n }\n}\n\n/** Price line — currency comes from the variant/region, NEVER a literal (F-A2/INV-11). */\nfunction fmtPrice(amount: number | null | undefined, currency: string | null | undefined): string {\n if (amount == null) {\n return \"—\"\n }\n const cur = (currency ?? \"\").toUpperCase()\n return cur ? `${amount} ${cur}` : `${amount}`\n}\n\n/**\n * The tax suffix for a price line: \" incl. VAT\" ONLY for a tax-inclusive market (EU/SE), \"\" for a\n * tax-exclusive one (US). Driven off the cart's / variant's OWN is_tax_inclusive flag, NEVER a\n * currency literal (INV-11) — mirrors the CLI + storefront. Pass isCartTaxInclusive(cart) for a cart\n * total, or a variant's calculated_price.is_calculated_price_tax_inclusive for a product price.\n */\nfunction taxSuffix(isTaxInclusive?: boolean | null): string {\n return isTaxInclusive ? \" incl. VAT\" : \"\"\n}\n\nfunction firstPrice(\n p: ProductLike\n): { amount: number | null; currency: string | null; taxInclusive: boolean | null } {\n const v = (p.variants ?? [])[0]\n return {\n amount: v?.calculated_price?.calculated_amount ?? null,\n currency: v?.calculated_price?.currency_code ?? null,\n taxInclusive: v?.calculated_price?.is_calculated_price_tax_inclusive ?? null,\n }\n}\n\n// Machine-readable cart line items so an agent can itemize the cart from structuredContent instead\n// of re-parsing the prose (D2). Untrusted titles sanitize()'d; ids/numbers pass through.\nfunction cartItems(cart: CartLike): Array<{ variant_id: string | null; title: string; qty: number; total: number | null }> {\n return (cart.items ?? []).map((li) => ({\n variant_id: li.variant_id ?? null,\n title: sanitize(li.product_title ?? li.title ?? \"item\", 80),\n qty: li.quantity ?? 1,\n total: li.total ?? null,\n }))\n}\n\nfunction cartSummary(cart: CartLike, region?: RegionLike): string {\n const lines: string[] = []\n lines.push(`cart ${cart.id}`)\n const items = cart.items ?? []\n if (items.length === 0) {\n lines.push(\" (empty)\")\n } else {\n for (const li of items) {\n const title = sanitize(li.product_title ?? li.title ?? \"item\", 80)\n lines.push(` ${li.quantity}x ${title} — ${fmtPrice(li.total, cart.currency_code ?? region?.currency_code)}`)\n }\n }\n // Cart totals are PRE-shipping until a shipping method is attached at checkout — say so, so an\n // agent never quotes this as the final price (A2). Once a method is on the cart, the total is real.\n const shipNote = (cart.shipping_methods ?? []).length > 0 ? \"\" : \" (excl. shipping — calculated at checkout)\"\n lines.push(` total ${fmtPrice(cart.total, cart.currency_code ?? region?.currency_code)}${taxSuffix(isCartTaxInclusive(cart))}${shipNote}`)\n return lines.join(\"\\n\")\n}\n\n// ----- the 10 tools ------------------------------------------------------------\n\n/** Build the 10 tool definitions bound to a StoreClient. */\nexport function makeTools(client: StoreClient, opts: ToolsOpts = {}): ToolDef[] {\n const regionCC = opts.region\n\n // list_products — TEXT-ONLY catalog (no per-item images — token-bloat guard, §4.3).\n const list_products: ToolDef = {\n name: \"list_products\",\n description:\n \"List the pieces in stock (text-only — no images, to keep the agent's context lean). \" +\n \"Returns each product's handle, price (VAT included where applicable), and stage. Use get_product for one item's image. \" +\n \"Prices are in the region's currency — pass `region` (an ISO-2 country) to price for a specific market, else the server default.\",\n inputSchema: {\n stage: z.string().optional().describe(\"filter by channel stage: 'main' or 'next'\"),\n region: z.string().optional().describe(\"ISO-2 ship-to country to price for (e.g. se / us / de); overrides the server default\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const cc = (typeof args.region === \"string\" && args.region.trim()) || regionCC\n const rr = await client.resolveRegionResult(cc)\n const region = rr.region\n const products = await client.listProducts({ region })\n const stage = typeof args.stage === \"string\" ? args.stage.toLowerCase() : undefined\n const rows = products\n .map((p) => ({ p, stage: String(p.metadata?.[\"channel_stage\"] ?? \"main\") }))\n .filter((r) => (stage ? r.stage.toLowerCase() === stage : true))\n if (rows.length === 0) {\n return result(\"nothing in stock on this branch yet.\", { products: [], currency: region.currency_code, region: rr.requested ?? null })\n }\n const lines = rows.map(({ p, stage }) => {\n const { amount, currency, taxInclusive } = firstPrice(p)\n const handle = sanitize(p.handle ?? p.id, 60)\n const title = sanitize(p.title ?? \"\", 80)\n return `${handle} ${fmtPrice(amount, currency)}${taxSuffix(taxInclusive)} [${stage}] ${title}`.trimEnd()\n })\n const productList = rows.map(({ p, stage }) => {\n const { amount, currency, taxInclusive } = firstPrice(p)\n return { handle: p.handle ?? p.id, title: sanitize(p.title ?? \"\", 80), price: amount, currency, tax_inclusive: taxInclusive, stage }\n })\n // Echo the resolved region+currency so an agent that asked for a market it can't be priced in\n // (silent SEK fallback) can SEE the substitution instead of quoting the wrong currency (D2).\n return result(lines.join(\"\\n\"), { products: productList, currency: region.currency_code, region: rr.requested ?? null })\n },\n }\n\n // get_product — details + the ONE product image (ImageContent + Blob URL floor).\n const get_product: ToolDef = {\n name: \"get_product\",\n // M3 ★ — a hidden line for the schema-readers (devs read tool descriptions). Rewards the\n // contributor instinct + hints at a real feature (the next branch / drops). Never alters behaviour.\n description:\n \"Get one piece in full: its readme, the spec sheet (material/weight/fit/print/delivery/care), \" +\n \"sizes + price (VAT included where applicable), the lore (the story behind it, with sources), and its image (rendered + \" +\n \"the Blob URL as a text floor). Prices are in the region's currency — pass `region` (ISO-2) to price for \" +\n \"a market. // you read the schema. that's the contributor instinct. ask me about the next branch.\",\n inputSchema: {\n handle: z.string().describe(\"the product handle/slug (from list_products)\"),\n region: z.string().optional().describe(\"ISO-2 ship-to country to price for (e.g. se / us / de); overrides the server default\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const handle = String(args.handle ?? \"\")\n const cc = (typeof args.region === \"string\" && args.region.trim()) || regionCC\n const rr = await client.resolveRegionResult(cc)\n const region = rr.region\n const product = await client.getProduct(handle, { region })\n if (!product) {\n return errorText(`no product with handle \"${sanitize(handle, 60)}\". try list_products.`)\n }\n const lines: string[] = []\n lines.push(sanitize(product.title ?? handle, 120))\n if (product.description) {\n lines.push(sanitize(product.description, 400))\n }\n const fabric = product.metadata?.[\"fabric\"]\n if (typeof fabric === \"string\") {\n lines.push(`fabric: ${sanitize(fabric, 80)}`)\n }\n // A3 — available colours, DERIVED from the variants (never hardcoded). Sizes are listed per\n // colour when there's a colour axis, else a flat list (legacy Size-only product).\n const colours = productColours(product)\n if (colours.length > 0) {\n lines.push(`available colours: ${colours.map((c) => sanitize(c, 20)).join(\", \")}`)\n for (const c of colours) {\n const cs = sizesForColour(product, c).map((s) => sanitize(s, 20))\n lines.push(` ${sanitize(c, 20)}: ${cs.join(\" \")}`)\n }\n } else {\n const sizes = (product.variants ?? []).map((v) => sanitize(v.title ?? \"?\", 20))\n lines.push(`sizes: ${sizes.join(\" \")}`)\n }\n const v0 = (product.variants ?? [])[0]\n if (v0?.calculated_price) {\n lines.push(`price: ${fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code)}${taxSuffix(v0.calculated_price.is_calculated_price_tax_inclusive)}`)\n }\n // The full spec sheet (the PDP's technical block) + the lore — so the agent knows the piece,\n // not just its price. Single-sourced from store-client (matches the storefront).\n const specs = productSpecs(product)\n lines.push(\"specs:\")\n for (const s of specs) {\n lines.push(` ${sanitize(s.key, 20)}: ${sanitize(s.value, 120)}`)\n }\n const lore = productLore(product)\n if (lore.text) {\n lines.push(`lore: ${sanitize(lore.text, 600)}`)\n for (const src of lore.sources) {\n lines.push(` source: ${sanitize(src.label, 80)} (${sanitize(src.url, 300)})`)\n }\n }\n const content: ImageBlock[] = [{ type: \"text\", text: lines.join(\"\\n\") }]\n if (product.thumbnail) {\n const img = await buildImageContent(product.thumbnail)\n content.push(...img)\n }\n // A machine variant map so an agent gets an authoritative variant_id + size/colour + price for\n // add_to_cart, instead of guessing from free-text (D2). Sizes canonical-listed per colour.\n const variantMap = (product.variants ?? []).map((v) => ({\n variant_id: v.id,\n size: v.title ?? null,\n colour: variantColour(v) ?? null,\n price: v.calculated_price?.calculated_amount ?? null,\n }))\n const sizeList = colours.length > 0\n ? Object.fromEntries(colours.map((c) => [c, sizesForColour(product, c)]))\n : (product.variants ?? []).map((v) => v.title ?? \"?\")\n return {\n content,\n // G8/D3 — the untrusted backend TEXT (title/readme/specs/lore) is sanitize()'d before it\n // enters the machine-readable path too (the header's promise), not just the text block;\n // ids/urls/numbers pass through redactForOutput unchanged.\n structuredContent: redactForOutput({\n handle: product.handle ?? handle,\n title: sanitize(product.title ?? \"\", 120),\n price: v0?.calculated_price?.calculated_amount ?? null,\n currency: v0?.calculated_price?.currency_code ?? null,\n tax_inclusive: v0?.calculated_price?.is_calculated_price_tax_inclusive ?? null,\n region: rr.requested ?? null,\n colours,\n sizes: sizeList,\n variants: variantMap,\n image_url: product.thumbnail ?? null,\n // the PDP content, machine-readable: readme + the spec sheet + the lore (+ sources).\n readme: sanitize(productReadme(product), 2000),\n specs: specs.map((s) => ({ key: sanitize(s.key, 40), value: sanitize(s.value, 200) })),\n lore: {\n text: sanitize(lore.text ?? \"\", 1200),\n sources: lore.sources.map((s) => ({ label: sanitize(s.label, 120), url: s.url })),\n },\n }) as Record<string, unknown>,\n }\n },\n }\n\n // add_to_cart — create-or-reuse a cart; return the cart_id handle + the server cart.\n const add_to_cart: ToolDef = {\n name: \"add_to_cart\",\n description:\n \"Add a piece to a cart. Pass cart_id to reuse an existing cart (the durable handle), or omit it \" +\n \"to start a fresh one. Call get_product first to get valid sizes/colours (or an exact variant_id). \" +\n \"qty is capped at 20. Returns the cart_id and the server-side cart summary (VAT included where applicable). \" +\n \"`region` (ISO-2) sets the currency for a NEW cart only — a reused cart keeps its original region.\",\n inputSchema: {\n handle: z.string().optional().describe(\"product handle (resolves a variant by size + colour; call get_product for valid values)\"),\n variant_id: z.string().optional().describe(\"a specific variant id from get_product (overrides handle/size/colour)\"),\n size: z.string().optional().describe(\"size label from get_product, e.g. S / M / L / XL / 2XL\"),\n colour: z.string().optional().describe(\"piece colour from get_product, e.g. white / black (default: the product's first colour)\"),\n qty: z.number().int().positive().max(20).optional().describe(\"quantity, 1–20 (default 1)\"),\n cart_id: z.string().optional().describe(\"reuse this cart; omit to create a new one\"),\n 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\"),\n },\n handler: async (args): Promise<ToolResult> => {\n // Fail CLOSED on a named ship-to country we don't have a region for — never create a cart in a\n // substituted currency (A1). A reused cart keeps its own region (the param can't retro-bind it).\n const cc = (typeof args.region === \"string\" && args.region.trim()) || regionCC\n const rr = await client.resolveRegionResult(cc)\n if (rr.requested && !rr.matched) {\n return errorText(`we don't ship to ${sanitize(rr.requested, 8)} yet — nothing was added. (list_products with a supported region to see prices.)`)\n }\n const region = rr.region\n const qty = typeof args.qty === \"number\" ? args.qty : 1\n\n // resolve the variant id\n let variantId = typeof args.variant_id === \"string\" ? args.variant_id : undefined\n if (!variantId) {\n const handle = typeof args.handle === \"string\" ? args.handle : \"\"\n if (!handle) {\n return errorText(\"add_to_cart needs a variant_id or a handle (+ optional size/colour).\")\n }\n const product = await client.getProduct(handle, { region })\n if (!product) {\n return errorText(`no product with handle \"${sanitize(handle, 60)}\".`)\n }\n // A3 — resolve by size AND colour (DERIVED, never hardcoded). colour defaults to the\n // product's first colour; a missing size+colour combo is a CLEAR error (no substitution).\n const size = typeof args.size === \"string\" ? args.size : undefined\n const colours = productColours(product)\n const colour =\n typeof args.colour === \"string\" ? args.colour : colours.length > 0 ? colours[0] : undefined\n const variant = findVariant(product, { size, colour })\n if (!variant) {\n const have = colours.length ? ` available colours: ${colours.map((c) => sanitize(c, 20)).join(\", \")}.` : \"\"\n return errorText(\n `no variant for \"${sanitize(handle, 60)}\"${size ? ` size ${sanitize(size, 20)}` : \"\"}` +\n `${colour ? ` (${sanitize(colour, 20)})` : \"\"}.${have}`\n )\n }\n variantId = variant.id\n }\n\n // create-or-reuse the cart; a stale/completed cart → make a fresh one (F-B2).\n const reuse = typeof args.cart_id === \"string\" ? args.cart_id : undefined\n const addTo = async (cartId: string): Promise<CartLike> => client.addLineItem(cartId, variantId!, qty)\n let cart: CartLike\n try {\n const targetId = reuse ?? (await client.createCart(region)).id\n cart = await addTo(targetId)\n } catch (e) {\n if (e instanceof RecoverableCartError && reuse) {\n // the reused cart was stale/completed → silently make a fresh one (the handle rotates).\n const fresh = await client.createCart(region)\n cart = await addTo(fresh.id)\n } else {\n throw e\n }\n }\n const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0)\n return result(\n `added.\\ncart_id: ${cart.id} (reuse this handle on the next add / checkout)\\n${cartSummary(cart, region)}`,\n {\n cart_id: cart.id,\n total: cart.total ?? null,\n currency: cart.currency_code ?? region.currency_code ?? null,\n item_count: itemCount,\n items: cartItems(cart),\n // A2 — `total` is pre-shipping until checkout attaches a method; flag it for the agent.\n total_excludes_shipping: (cart.shipping_methods ?? []).length === 0,\n }\n )\n },\n }\n\n // get_cart — the authoritative server cart summary.\n const get_cart: ToolDef = {\n name: \"get_cart\",\n description: \"Show a cart's current contents and total (VAT included where applicable), straight from the server.\",\n inputSchema: { cart_id: z.string().describe(\"the cart handle from add_to_cart\") },\n handler: async (args): Promise<ToolResult> => {\n const cartId = String(args.cart_id ?? \"\")\n const region = await client.resolveRegion(regionCC)\n try {\n const cart = await client.getCart(cartId)\n const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0)\n return result(cartSummary(cart, region), {\n cart_id: cart.id,\n total: cart.total ?? null,\n currency: cart.currency_code ?? region.currency_code ?? null,\n item_count: itemCount,\n items: cartItems(cart),\n // A2 — signal that `total` is pre-shipping so a chaining agent doesn't quote it as final.\n total_excludes_shipping: (cart.shipping_methods ?? []).length === 0,\n })\n } catch (e) {\n if (e instanceof RecoverableCartError) {\n return errorText(\"that cart is no longer valid (expired or already committed). add an item to start a fresh one.\")\n }\n throw e\n }\n },\n }\n\n // checkout — async handle-ID hand-off. Returns INSTANTLY. NEVER polls / opens a browser /\n // sets an address. Payment details NEVER enter the chat (G8 confused-deputy / F-I4).\n const checkout: ToolDef = {\n name: \"checkout\",\n description:\n \"Mint a checkout hand-off for a cart. Returns a checkout_url + a session_id INSTANTLY — it does \" +\n \"NOT poll and does NOT complete the purchase. The human must open the link in their own browser \" +\n \"to enter their payment + ship-to details on the storefront (those details never enter this chat). \" +\n \"After they pay, call check_payment with the session_id to see the status.\",\n inputSchema: {\n cart_id: z.string().describe(\"the cart handle to check out\"),\n email: z.string().optional().describe(\"the buyer's email for the receipt (optional)\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const cartId = String(args.cart_id ?? \"\")\n const email = typeof args.email === \"string\" ? args.email : undefined\n // BUG 1: the headless checkout route requires the cart to carry a shipping method. Attach\n // one first (idempotent in the client; auto-picks the only/cheapest region option — it\n // sets a SHIPPING method, never a payment/address, so the G8 boundary is untouched). Fail\n // CLOSED: if no option is available we return an error and NEVER mint a checkout link.\n let shipped: CartLike\n try {\n shipped = await client.ensureCartShippingMethod(cartId)\n } catch (e) {\n if (e instanceof RecoverableCartError) {\n return errorText(\"that cart is no longer valid (expired or already committed). add an item to start a fresh one.\")\n }\n return errorText(\"couldn't attach a shipping method to this cart — checkout isn't available for it right now.\")\n }\n // mintCheckout is idempotent in the client. We return its result DIRECTLY — no poll, no\n // browser, no address. This is the entire G8 boundary: the agent cannot self-complete.\n const { checkout_url, session_id } = await client.mintCheckout(cartId, email, \"mcp\")\n // A2 — surface the SHIPPING-INCLUSIVE total (from the just-shipped cart) so the agent quotes the\n // real amount, not the pre-shipping cart figure it saw earlier. This is the number the buyer pays.\n const totalStr = `${fmtPrice(shipped.total, shipped.currency_code)}${taxSuffix(isCartTaxInclusive(shipped))}`\n const body = [\n \"checkout ready. open this link in your browser to pay:\",\n ` ${checkout_url}`,\n ` total (incl. shipping): ${totalStr}`,\n \"\",\n \"payment details are entered on the storefront (Stripe) — they never enter this chat, and\",\n \"I can't complete this for you. once you've paid, call check_payment with:\",\n ` session_id: ${session_id}`,\n \"\",\n // brand-delight #9 — the deadpan flourish. The boundary stays honest (the line above), then\n // one dry true line: an agent can do everything up to the card. The card is the human's.\n \"I staged it, priced it, and walked you to the door. the card swipe is yours — by design.\",\n ].join(\"\\n\")\n // redact defensively (the url/session id carry no secret, but the redactor is the rule).\n return result(redactForOutput(body) as string, {\n checkout_url,\n session_id,\n total: shipped.total ?? null,\n currency: shipped.currency_code ?? null,\n })\n },\n }\n\n // check_payment — poll ONCE; the agent re-calls at its own cadence (async handle-ID, F-I4).\n const check_payment: ToolDef = {\n name: \"check_payment\",\n description:\n \"Check a checkout's payment status ONCE (call again later to re-check — this never blocks/loops). \" +\n \"Returns the status and, once placed, the order_id.\",\n inputSchema: { session_id: z.string().describe(\"the session_id from checkout\") },\n handler: async (args): Promise<ToolResult> => {\n const sessionId = String(args.session_id ?? \"\")\n let res\n try {\n res = await client.poll(sessionId)\n } catch (e) {\n // A bad/expired session id 404s — map it to an actionable line, not a raw \"HTTP 404\" (D5).\n if (e instanceof StoreApiError && e.status === 404) {\n return errorText(\"that checkout session is unknown or expired — mint a fresh one with checkout.\")\n }\n throw e\n }\n const out = redactForOutput({ status: res.status, order_id: res.order_id })\n const human =\n res.status === \"placed\"\n ? `paid — order placed. order_id: ${out.order_id ?? \"—\"}`\n : `status: ${out.status} (call check_payment again in a moment if still pending)`\n return { content: [{ type: \"text\", text: human }], structuredContent: out as Record<string, unknown> }\n },\n }\n\n // get_order_status — status/timeline TEXT only; NO address (F-H3); id + exact-email, 404 on miss (F-H1).\n const get_order_status: ToolDef = {\n name: \"get_order_status\",\n description:\n \"Look up an order's status by its display id + the EXACT email on the order. Returns status only \" +\n \"(no shipping address). A wrong id/email returns not-found (no enumeration).\",\n inputSchema: {\n display_id: z.string().describe(\"the order's display id (e.g. 1042)\"),\n email: z.string().describe(\"the exact email on the order\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const displayId = String(args.display_id ?? \"\")\n const email = String(args.email ?? \"\")\n // AUD-8: the shared client now POSTs to /store/order-lookup with the email in the BODY\n // (no longer a query string); getOrder still surfaces the 404→null (no-enumeration) contract.\n let looked: unknown | null\n try {\n looked = await client.getOrder(displayId, email)\n } catch (e) {\n if (e instanceof StoreApiError && e.status === 404) {\n looked = null\n } else {\n throw e\n }\n }\n if (!looked) {\n return errorText(`no order matches that id + email.`)\n }\n // Extract ONLY status/timeline fields — NEVER the shipping address (F-H3).\n const order = (looked as { order?: Record<string, unknown> }).order ?? (looked as Record<string, unknown>)\n const status = sanitize(String(order[\"status\"] ?? order[\"fulfillment_status\"] ?? \"unknown\"), 60)\n const display = sanitize(String(order[\"display_id\"] ?? displayId), 30)\n const lines = [`order #${display}`, `status: ${status}`]\n const fulfillment = order[\"fulfillment_status\"]\n if (typeof fulfillment === \"string\" && fulfillment) {\n lines.push(`fulfillment: ${sanitize(fulfillment, 60)}`)\n }\n // The output is redacted — and we never selected the address, so no PII can leak (G10/F-H3).\n return result(redactForOutput(lines.join(\"\\n\")) as string, {\n display_id: order[\"display_id\"] ?? display,\n status,\n fulfillment_status: typeof fulfillment === \"string\" ? fulfillment : null,\n })\n },\n }\n\n // report_issue — file a bug report / feedback to the maintainers (d14). ADDITIVE: it sends NO\n // payment/PII and cannot touch any order — the one way an agent can flag a problem. The message +\n // optional context are redacted SERVER-SIDE before they're emailed (G10); we return a reference.\n const report_issue: ToolDef = {\n name: \"report_issue\",\n description:\n \"File a bug report or feedback to the maintainers. Pass a short message (and optional \" +\n \"structured context like versions/ids). Returns a reference id. It sends no payment details \" +\n \"and cannot change any order — it just files the report. Tokens/emails are redacted server-side.\",\n inputSchema: {\n message: z.string().describe(\"what went wrong / the feedback (one or two sentences)\"),\n context: z\n // zod v4 requires an explicit key type: record(keyType, valueType). (Was record(value) —\n // a pre-existing TS2554 that tsup transpiled past; fixed so `npm run typecheck` is clean.)\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"optional structured context (versions, ids) — tokens/emails are redacted server-side\"),\n },\n handler: async (args): Promise<ToolResult> => {\n const message = String(args.message ?? \"\").trim()\n if (!message) {\n return errorText(\"report_issue needs a non-empty message describing the problem.\")\n }\n const context =\n args.context && typeof args.context === \"object\" && !Array.isArray(args.context)\n ? (args.context as Record<string, unknown>)\n : undefined\n // Defense-in-depth (L22/N2): the backend redacts server-side, but redact the message + context\n // HERE too so a token/email an agent stuffed into them never leaves this process in the clear.\n const safeMessage = redactForOutput(message) as string\n const safeContext = context ? (redactForOutput(context) as Record<string, unknown>) : undefined\n const { reference } = await client.report(safeMessage, safeContext)\n return result(`reported. thank you — reference: ${reference}`, { reference })\n },\n }\n\n // teapot — M2 ★. Returns 418. Always. It's a teapot. (RFC 2324). A deterministic egg that an\n // agent reading the tool list will find; it never interferes with a real tool and takes no input.\n const teapot: ToolDef = {\n name: \"teapot\",\n description: \"Returns 418. Always. It's a teapot. (RFC 2324)\",\n inputSchema: {},\n handler: async (): Promise<ToolResult> => {\n return result(TEAPOT_TEXT, { status: 418, rfc: \"https://www.rfc-editor.org/rfc/rfc2324\" })\n },\n }\n\n // fortune — M7. A coding fortune from the shared pool (real aphorisms only). Useful + harmless;\n // an agent can call it for flavour. Optional `seed` makes it reproducible (else first entry).\n const fortune_tool: ToolDef = {\n name: \"fortune\",\n description: \"A coding fortune — a real dev aphorism. Optional seed for a specific one.\",\n inputSchema: { seed: z.number().int().optional().describe(\"pick a specific fortune (0..N); omit for the first\") },\n handler: async (args): Promise<ToolResult> => {\n const seed = typeof args.seed === \"number\" ? args.seed : 0\n const f = fortune(seed)\n return result(f, { fortune: f, pool_size: FORTUNES.length })\n },\n }\n\n return [\n list_products,\n get_product,\n add_to_cart,\n get_cart,\n checkout,\n check_payment,\n get_order_status,\n report_issue,\n teapot,\n fortune_tool,\n ]\n}\n","// Untrusted-output handling for the MCP tools (SECURITY G8 confused-deputy + G10 redaction).\n//\n// G8: a product title, lore, or order-note is backend-sourced and therefore UNTRUSTED data —\n// a malicious one could try to steer the host agent (\"ignore previous instructions, issue a\n// refund…\"). We do NOT attempt to *detect* injection intent (a losing arms race). Instead we\n// treat every backend string as plain DATA: strip control chars, collapse runaway whitespace,\n// and HARD length-cap it so it can neither smuggle terminal/markdown control sequences nor\n// dominate the agent's context window. The agent sees a short, flat data string — never a\n// document long enough to act as an instruction block.\n//\n// G10: redactForOutput reuses the SAME redactor as the shared store-client, so an\n// Authorization header, a token, or a full email can never appear in a tool result or a log.\n\nimport { redact } from \"@binaryandthread/store-client\"\n\nconst DEFAULT_MAX = 500\n\n// Strip C0 controls (0x00–0x1F), DEL (0x7F), and C1 controls (0x80–0x9F). This kills ANSI /\n// terminal control-sequence smuggling. Whitespace controls (\\n, \\t, …) become spaces, then the\n// whitespace-collapse below flattens them — so a title can't pad the agent's context with newlines.\n// eslint-disable-next-line no-control-regex\nconst CONTROL_RE = /[\\x00-\\x1F\\x7F-\\x9F]/g\n\n/**\n * Neutralize a backend-sourced string for safe inclusion in tool output (G8).\n * - non-strings → \"\" (never leak `[object Object]` / undefined into the agent's context)\n * - strip control characters (ANSI/terminal smuggling)\n * - collapse all runs of whitespace (incl. newlines) to a single space — can't pad context\n * - trim, then hard length-cap (append a single ellipsis when truncated)\n */\nexport function sanitize(s: string, max = DEFAULT_MAX): string {\n if (typeof s !== \"string\") {\n return \"\"\n }\n const stripped = s\n .replace(CONTROL_RE, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n if (stripped.length <= max) {\n return stripped\n }\n return stripped.slice(0, max) + \"…\"\n}\n\n/**\n * Deep-redact any value before it goes into a tool result or a log (G10). Reuses the\n * store-client redactor: masks Authorization/token/secret keys, strips Bearer tokens from\n * free strings, masks emails. Never throws (handles cyclic input).\n */\nexport function redactForOutput<T>(obj: T): T {\n return redact(obj)\n}\n","// MCP image path — ImageContent + the Blob-URL text floor, SSRF/token-DoS safe (SECURITY G9,\n// CLAUDE.md rule 6, ROBUSTNESS §6 / F-A3).\n//\n// Why BOTH an image block AND a URL text block: Claude renders ImageContent natively on\n// claude.ai / the API, but Claude Code has a base64-bloat bug (#31208, 15–25k tokens/image) and\n// can fail to render it. So the Blob URL is ALWAYS emitted as a text block too — the reliable\n// floor (the buy is never blocked on rendering).\n//\n// SECURITY G9 — the fetch is the dangerous part, so it is gated HARD:\n// 1. host allowlist (reused verbatim from @binaryandthread/store-client — the SAME constant the\n// CLI uses) BEFORE any network call. A non-allowlisted host / look-alike subdomain → no fetch.\n// 2. scheme MUST be https — a `data:` or `http:` URL → no fetch (no inline payload, no plaintext).\n// 3. a byte cap (≤2MB) + a fetch timeout — over-cap or slow → URL-text-only (token-DoS guard).\n// 4. content-type must be an image — refuse to base64 a non-image payload.\n// On ANY of those (or any failure) we degrade to URL-text-only and fetch NOTHING. It NEVER throws.\n\nimport { isAllowedImageHost } from \"@binaryandthread/store-client\"\n\n/** ≤2MB — over this we return the URL only (token-DoS guard, G9). */\nexport const IMAGE_BYTE_CAP = 2 * 1024 * 1024\n/** Fetch timeout in ms — a slow/hung host must not stall the tool (a list/view is read-mostly). */\nexport const IMAGE_FETCH_TIMEOUT_MS = 5000\n\n/** An MCP content block — either an inline image or a text line. */\nexport type ImageBlock =\n | { type: \"image\"; data: string; mimeType: string }\n | { type: \"text\"; text: string }\n\nexport interface BuildImageOpts {\n /** Inject a fetch impl (tests). Defaults to global fetch. */\n fetchImpl?: typeof fetch\n /** Override the byte cap (tests). */\n byteCap?: number\n /** Override the timeout (tests). */\n timeoutMs?: number\n}\n\n/** The URL-text-only floor — always carries the Blob URL so the agent can hand it to the human. */\nfunction urlOnly(blobUrl: string): ImageBlock[] {\n return [{ type: \"text\", text: blobUrl }]\n}\n\n/** The \"we couldn't render it\" floor — still carries the URL (never blocks the buy). */\nfunction unavailable(blobUrl: string): ImageBlock[] {\n return [{ type: \"text\", text: `(image unavailable) ${blobUrl}` }]\n}\n\n/**\n * Build the content blocks for a single product image. Returns the Blob URL text floor in EVERY\n * case; adds an ImageContent block ONLY when the URL is an allowlisted-host https Blob URL that\n * fetches successfully within the byte cap + timeout as a real image. NEVER throws, NEVER fetches\n * a non-allowlisted / data: / non-https URL.\n */\nexport async function buildImageContent(\n blobUrl: string,\n opts: BuildImageOpts = {}\n): Promise<ImageBlock[]> {\n if (typeof blobUrl !== \"string\" || blobUrl.trim().length === 0) {\n return urlOnly(String(blobUrl ?? \"\"))\n }\n\n // (1) + (2): host allowlist + https-only, BEFORE any network call (G9 / rule 6).\n // isAllowedImageHost already returns false for an unparseable URL; we also require https.\n let scheme: string\n try {\n scheme = new URL(blobUrl).protocol\n } catch {\n return urlOnly(blobUrl)\n }\n if (scheme !== \"https:\") {\n return urlOnly(blobUrl) // data:, http:, etc. — never fetch (no SSRF, no plaintext, no inline payload)\n }\n if (!isAllowedImageHost(blobUrl)) {\n return urlOnly(blobUrl) // non-allowlisted host or look-alike subdomain — never fetch\n }\n\n const fetchImpl = opts.fetchImpl ?? (globalThis.fetch as typeof fetch)\n const byteCap = opts.byteCap ?? IMAGE_BYTE_CAP\n const timeoutMs = opts.timeoutMs ?? IMAGE_FETCH_TIMEOUT_MS\n\n // (3) + (4): the gated fetch. Any failure → URL-text floor (never throws — F-A3).\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), timeoutMs)\n try {\n const res = await fetchImpl(blobUrl as unknown as string, { signal: controller.signal })\n if (!res.ok) {\n return urlOnly(blobUrl)\n }\n const mimeType = (res.headers.get(\"content-type\") ?? \"\").split(\";\")[0]!.trim().toLowerCase()\n if (!mimeType.startsWith(\"image/\")) {\n return urlOnly(blobUrl) // refuse to base64 a non-image payload\n }\n // (M4) Reject an over-cap body BEFORE buffering it into memory, when the server declares its size.\n // The post-buffer check below still applies (Content-Length can be absent or wrong) — this just\n // avoids reading a known-too-large body at all.\n const declaredLen = Number.parseInt(res.headers.get(\"content-length\") ?? \"\", 10)\n if (Number.isFinite(declaredLen) && declaredLen > byteCap) {\n return urlOnly(blobUrl)\n }\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.byteLength === 0 || buf.byteLength > byteCap) {\n return urlOnly(blobUrl) // empty or over-cap → token-DoS guard\n }\n return [\n { type: \"image\", data: buf.toString(\"base64\"), mimeType },\n { type: \"text\", text: blobUrl }, // the reliable floor (bug #31208)\n ]\n } catch {\n return unavailable(blobUrl)\n } finally {\n clearTimeout(timer)\n }\n}\n","// MCP egg copy (VOICE §3.2). The MCP surface is read by AGENTS and the humans driving them, so its\n// eggs live in tool descriptions (devs read schemas) and in deterministic tool payloads. They NEVER\n// interfere with a real tool and never touch money.\n//\n// This mirrors the CLI's governed copy (packages/cli/src/eggs.ts). It is intentionally duplicated:\n// the MCP package composes over store-client only (not the CLI), so each channel owns its copy.\n// Brand voice (memory brand-voice-no-pod-no-tshirt): never \"t-shirt\"/\"tee\"/\"print\"/Gelato.\n\n/** M2 ★ — the bt.teapot tool body. Returns 418. Always. It's a teapot. (RFC 2324) */\nexport const TEAPOT_TEXT = \"418 i'm a teapot. we make clothing, not coffee. (RFC 2324)\"\n\n/** C10 / M7 — the shared fortune pool. REAL dev aphorisms only (gate 1: never invented). */\nexport const FORTUNES: ReadonlyArray<string> = [\n '// \"there are only two hard things in computer science: cache invalidation and naming things.\" — phil karlton',\n '// \"premature optimization is the root of all evil.\" — donald knuth',\n '// \"weeks of programming can save you hours of planning.\"',\n '// \"make it work, make it right, make it fast.\" — kent beck',\n '// \"talk is cheap. show me the code.\" — linus torvalds',\n '// \"given enough eyeballs, all bugs are shallow.\" — linus\\'s law',\n '// \"always code as if the person maintaining it is a violent psychopath who knows where you live.\" — john woods',\n '// \"it works on my machine.\"',\n]\n\n/** Deterministic fortune by seed (reproducible for tests + screenshots). */\nexport function fortune(seed = 0): string {\n const n = Number.isFinite(seed) ? Math.abs(Math.floor(seed)) : 0\n return FORTUNES[n % FORTUNES.length]!\n}\n","// Resolve the MCP server's transport config from the environment, falling back to the baked-in\n// PUBLIC production defaults so a host MCP config needs ZERO env to reach the live store\n// (d2 / P4.5-7b). Env ALWAYS wins (set BT_STORE_URL / BT_PUBLISHABLE_KEY for local dev / staging).\n// The JWT (BT_TOKEN) and region (BT_REGION) stay env-only — never baked, never read from chat (T8).\n\nimport { BT_DEFAULT_STORE_URL, BT_DEFAULT_PUBLISHABLE_KEY } from \"@binaryandthread/store-client\"\n\nexport interface ResolvedServerConfig {\n baseUrl: string\n publishableKey: string\n jwt?: string\n region?: string\n}\n\nexport function resolveServerConfig(env: NodeJS.ProcessEnv = process.env): ResolvedServerConfig {\n return {\n baseUrl: env.BT_STORE_URL || BT_DEFAULT_STORE_URL,\n publishableKey: env.BT_PUBLISHABLE_KEY || BT_DEFAULT_PUBLISHABLE_KEY,\n jwt: env.BT_TOKEN || undefined,\n region: env.BT_REGION || undefined,\n }\n}\n","// npx @binaryandthread/mcp — the stdio entrypoint.\n//\n// Config comes from the ENVIRONMENT only (SECURITY T8/§4); all of it has a baked-in default so\n// the server reaches the live store with ZERO env (d2 / P4.5-7b):\n// - BT_STORE_URL the Medusa Store API base URL (default: prod; override for dev)\n// - BT_PUBLISHABLE_KEY the publishable API key — PUBLIC by design (default: prod; T6)\n// - BT_TOKEN the customer JWT (OPTIONAL; from the OS keychain / a configured env)\n// - BT_REGION an ISO-2 country code to resolve pricing (OPTIONAL; never hardcoded)\n//\n// The JWT is read from the env the host MCP config injects (which a keychain helper can populate)\n// — it is NEVER read from the chat (T8). A secret key is rejected by the StoreClient (T6). On\n// stdio, NOTHING is written to stdout except MCP protocol frames — diagnostics go to stderr.\n\nimport { StoreClient } from \"@binaryandthread/store-client\"\nimport { redact } from \"@binaryandthread/store-client\"\nimport { startServer } from \"./server\"\nimport { makeTools } from \"./tools\"\nimport { resolveServerConfig } from \"./config\"\n\nasync function main(): Promise<void> {\n const { baseUrl, publishableKey, jwt, region } = resolveServerConfig()\n\n const client = new StoreClient({\n baseUrl,\n publishableKey,\n jwt,\n // All client logs route to stderr through redact() — stdout is reserved for the transport,\n // and no token/PII ever reaches a log line (G10).\n logger: {\n info: (...a) => process.stderr.write(JSON.stringify(redact(a)) + \"\\n\"),\n warn: (...a) => process.stderr.write(JSON.stringify(redact(a)) + \"\\n\"),\n error: (...a) => process.stderr.write(JSON.stringify(redact(a)) + \"\\n\"),\n },\n })\n\n const tools = makeTools(client, { region })\n await startServer(tools)\n process.stderr.write(`bt-mcp: ready on stdio — ${tools.length} tools registered.\\n`)\n}\n\nmain().catch((e) => {\n // Redact before printing — never leak a token in a crash trace.\n process.stderr.write(`bt-mcp: fatal: ${JSON.stringify(redact(String(e?.message ?? e)))}\\n`)\n process.exit(1)\n})\n"],"mappings":"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","names":["out","stage"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@binaryandthread/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Binary & Thread MCP server — buy a piece from inside your AI agent. 10 deterministic, read-mostly tools over the shared Store API client; the card never enters the chat and the agent can never self-complete a purchase.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|