@binaryandthread/mcp 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -302,6 +302,7 @@ var StoreClient = class {
302
302
  if (match) {
303
303
  return match;
304
304
  }
305
+ this.logWarn("store.region.unmatched", { countryCode: cc, fallback: regions[0].id });
305
306
  }
306
307
  return regions[0];
307
308
  }
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","../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 // A country code was given but no region is configured for it → we're about to fall back to\n // region #1 and price in ITS currency. Warn (stderr, redacted) so the mis-resolution is\n // observable instead of a silent wrong-currency cart. Matters as we go intl (F-A2 / Phase 6).\n this.logWarn(\"store.region.unmatched\", { countryCode: cc, fallback: regions[0]!.id })\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@binaryandthread/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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",