cope-sdk 0.1.0 → 0.1.1
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/CHANGELOG.md +74 -1
- package/README.md +94 -41
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -10
- package/dist/index.d.ts +55 -10
- package/dist/index.global.js +1 -2
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/node.cjs +59 -20
- package/dist/mcp/node.cjs.map +1 -1
- package/dist/mcp/node.js +59 -20
- package/dist/mcp/node.js.map +1 -1
- package/dist/mcp.cjs +59 -20
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +59 -20
- package/dist/mcp.js.map +1 -1
- package/package.json +2 -1
package/dist/mcp.js
CHANGED
|
@@ -180,7 +180,7 @@ var CHECKOUT_MOUNT_MAX_ATTEMPTS = 20;
|
|
|
180
180
|
var CHECKOUT_READY_TIMEOUT_MS = 8e3;
|
|
181
181
|
var SUPPORTED_ONE_CLICK_CHARGE_TERMS_VERSIONS = /* @__PURE__ */ new Set(["oto-v1"]);
|
|
182
182
|
function seg(id) {
|
|
183
|
-
return encodeURIComponent(id);
|
|
183
|
+
return encodeURIComponent(String(id));
|
|
184
184
|
}
|
|
185
185
|
function isRecord2(value) {
|
|
186
186
|
return typeof value === "object" && value !== null;
|
|
@@ -212,10 +212,28 @@ function parseErrorBody(body) {
|
|
|
212
212
|
function isApiErrorEntry(value) {
|
|
213
213
|
return isRecord2(value) && typeof value.code === "string" && typeof value.message === "string";
|
|
214
214
|
}
|
|
215
|
+
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
216
|
+
var ASCTIME = /^[A-Z][a-z]{2} ([A-Z][a-z]{2}) ([ \d]\d) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/;
|
|
217
|
+
function asctimeToUtc([, monthName, day, hh, mm, ss, year]) {
|
|
218
|
+
const fields = [Number(year), MONTHS.indexOf(monthName ?? ""), Number((day ?? "").trim()), Number(hh), Number(mm), Number(ss)];
|
|
219
|
+
const at = Date.UTC(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5]);
|
|
220
|
+
const back = new Date(at);
|
|
221
|
+
const roundTrip = [
|
|
222
|
+
back.getUTCFullYear(),
|
|
223
|
+
back.getUTCMonth(),
|
|
224
|
+
back.getUTCDate(),
|
|
225
|
+
back.getUTCHours(),
|
|
226
|
+
back.getUTCMinutes(),
|
|
227
|
+
back.getUTCSeconds()
|
|
228
|
+
];
|
|
229
|
+
return roundTrip.every((value, i) => value === fields[i]) ? at : Number.NaN;
|
|
230
|
+
}
|
|
215
231
|
function parseRetryAfter(value) {
|
|
216
|
-
if (value === null
|
|
217
|
-
|
|
218
|
-
|
|
232
|
+
if (value === null) return null;
|
|
233
|
+
const trimmed = value.trim();
|
|
234
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
235
|
+
const asctime = ASCTIME.exec(trimmed);
|
|
236
|
+
const at = asctime ? asctimeToUtc(asctime) : / GMT$/.test(trimmed) ? Date.parse(trimmed) : Number.NaN;
|
|
219
237
|
return Number.isNaN(at) ? null : Math.max(0, Math.ceil((at - Date.now()) / 1e3));
|
|
220
238
|
}
|
|
221
239
|
function bodyRequestId(body) {
|
|
@@ -386,7 +404,7 @@ function parseCheckoutEmbedEvent(data) {
|
|
|
386
404
|
}
|
|
387
405
|
return null;
|
|
388
406
|
}
|
|
389
|
-
var
|
|
407
|
+
var Cope = class {
|
|
390
408
|
publishableKey;
|
|
391
409
|
baseUrl;
|
|
392
410
|
checkoutBaseUrl;
|
|
@@ -404,7 +422,7 @@ var CopeCart = class {
|
|
|
404
422
|
throw new CopeError('Invalid publishableKey. Must start with "cope_pk_".');
|
|
405
423
|
}
|
|
406
424
|
if (typeof window !== "undefined" && window.location?.protocol === "http:" && window.location.hostname !== "localhost") {
|
|
407
|
-
throw new CopeError("
|
|
425
|
+
throw new CopeError("COPE SDK requires HTTPS. Refusing to operate on an insecure page.");
|
|
408
426
|
}
|
|
409
427
|
this.publishableKey = options.publishableKey;
|
|
410
428
|
const baseOrigin = originOnly(options.baseUrl ?? DEFAULT_BASE_URL);
|
|
@@ -442,9 +460,11 @@ var CopeCart = class {
|
|
|
442
460
|
async addLine(cartId, payload) {
|
|
443
461
|
return normalizeCart(await this.post(`/carts/${seg(cartId)}/lines`, payload));
|
|
444
462
|
}
|
|
463
|
+
/** `lineId` is the line's `id`, an integer; a string of its digits is accepted too. */
|
|
445
464
|
async updateLine(cartId, lineId2, payload) {
|
|
446
465
|
return normalizeCart(await this.patch(`/carts/${seg(cartId)}/lines/${seg(lineId2)}`, payload));
|
|
447
466
|
}
|
|
467
|
+
/** `lineId` is the line's `id`, an integer; a string of its digits is accepted too. */
|
|
448
468
|
async removeLine(cartId, lineId2) {
|
|
449
469
|
return normalizeCart(await this.del(`/carts/${seg(cartId)}/lines/${seg(lineId2)}`));
|
|
450
470
|
}
|
|
@@ -476,6 +496,7 @@ var CopeCart = class {
|
|
|
476
496
|
...input.attribution ? { attribution: input.attribution } : {},
|
|
477
497
|
...input.intendedPaymentMethod != null ? { intended_payment_method: input.intendedPaymentMethod } : {},
|
|
478
498
|
...input.metadata ? { metadata: input.metadata } : {},
|
|
499
|
+
...input.collectBuyerIdentityAtCheckout != null ? { collect_buyer_identity_at_checkout: input.collectBuyerIdentityAtCheckout } : {},
|
|
479
500
|
lines: lines.map((line) => ({
|
|
480
501
|
product_id: line.productId,
|
|
481
502
|
...line.planId !== void 0 ? { plan_id: line.planId } : {},
|
|
@@ -499,7 +520,8 @@ var CopeCart = class {
|
|
|
499
520
|
status: checkout.status,
|
|
500
521
|
expiresAt: checkout.expiresAt,
|
|
501
522
|
totals: checkout.totals,
|
|
502
|
-
...input.intendedPaymentMethod != null ? { intendedPaymentMethod: input.intendedPaymentMethod } : {}
|
|
523
|
+
...input.intendedPaymentMethod != null ? { intendedPaymentMethod: input.intendedPaymentMethod } : {},
|
|
524
|
+
...raw.field_errors?.length ? { fieldErrors: raw.field_errors } : {}
|
|
503
525
|
};
|
|
504
526
|
}
|
|
505
527
|
async cancelCheckout(checkoutId) {
|
|
@@ -982,15 +1004,20 @@ function projectLine(line) {
|
|
|
982
1004
|
unitPriceMinorUnits: line.price_snapshot?.unit_price_minor_units ?? null
|
|
983
1005
|
};
|
|
984
1006
|
}
|
|
985
|
-
function projectCheckout(checkout) {
|
|
1007
|
+
function projectCheckout(checkout, lines) {
|
|
986
1008
|
return {
|
|
987
1009
|
checkoutUrl: checkout.checkoutUrl,
|
|
988
1010
|
status: checkout.status,
|
|
989
1011
|
expiresAt: checkout.expiresAt,
|
|
990
1012
|
totalMinorUnits: checkout.totals.total_minor_units,
|
|
991
|
-
currency: checkout.totals.currency
|
|
1013
|
+
currency: checkout.totals.currency,
|
|
1014
|
+
buyerNext: buyerNextFor(lines)
|
|
992
1015
|
};
|
|
993
1016
|
}
|
|
1017
|
+
function buyerNextFor(lines) {
|
|
1018
|
+
const waiver = lines.some((line) => line.requires_shipping === false) ? ", waive their right of withdrawal (a product in this cart does not ship)" : lines.some((line) => line.requires_shipping === void 0) ? ", waive their right of withdrawal if a product in the cart does not ship" : "";
|
|
1019
|
+
return `Send the buyer to checkoutUrl. There they accept the terms${waiver}, and pay. You cannot do any of these for them.`;
|
|
1020
|
+
}
|
|
994
1021
|
var GUIDANCE = {
|
|
995
1022
|
invalid_cart_ref: "The cartRef is not valid here. Start again with create_cart.",
|
|
996
1023
|
cart_expired: "This cart is finished: ordered (the buyer has paid), expired or abandoned. For a new purchase, start again with create_cart.",
|
|
@@ -1004,6 +1031,7 @@ var GUIDANCE = {
|
|
|
1004
1031
|
offer_sourced_no_promo: "This cart comes from a negotiated offer; promo codes do not apply.",
|
|
1005
1032
|
checkout_expired: "The checkout expired. Call start_checkout again.",
|
|
1006
1033
|
tax_address_invalid: "The buyer address is incomplete or invalid for tax. Ask the buyer and call set_buyer_identity.",
|
|
1034
|
+
missing_tax_location: "Totals need the buyer's country and postal code. Ask the buyer and call set_buyer_identity, then get_cart.",
|
|
1007
1035
|
tax_service_unavailable: "Tax could not be calculated right now. Retry shortly.",
|
|
1008
1036
|
business_checkout_hold: "This store cannot take orders right now. Tell the buyer to try later.",
|
|
1009
1037
|
rate_limited: "Too many requests. Wait retryAfter seconds, then retry.",
|
|
@@ -1054,7 +1082,7 @@ var CONFLICT_WAITS_MS = [500, 1e3, 2e3];
|
|
|
1054
1082
|
var cartRef = z.string().min(1).max(2048).describe("The cartRef returned by create_cart. Pass it back unchanged.");
|
|
1055
1083
|
var ID = /^[A-Za-z0-9_-]{1,100}$/;
|
|
1056
1084
|
var productId = z.string().regex(ID).describe("The product id, e.g. from the store page");
|
|
1057
|
-
var lineId = z.string().regex(
|
|
1085
|
+
var lineId = z.union([z.number().int().positive(), z.string().regex(/^\d{1,19}$/)]).describe("A lineId from the cart, as get_cart returned it");
|
|
1058
1086
|
var address = z.object({
|
|
1059
1087
|
line1: z.string(),
|
|
1060
1088
|
line2: z.string().optional(),
|
|
@@ -1071,10 +1099,10 @@ function registerCheckoutTools(server, ctx) {
|
|
|
1071
1099
|
return refusal(error) ?? fail(error);
|
|
1072
1100
|
}
|
|
1073
1101
|
};
|
|
1074
|
-
const newClient = () => new
|
|
1102
|
+
const newClient = () => new Cope({ ...ctx.clientOptions, persistence: "none" });
|
|
1075
1103
|
const openCart = async (ref) => {
|
|
1076
1104
|
const creds = await ctx.codec.decode(ref);
|
|
1077
|
-
return { creds, cope: new
|
|
1105
|
+
return { creds, cope: new Cope({ ...ctx.clientOptions, cart: creds }) };
|
|
1078
1106
|
};
|
|
1079
1107
|
server.registerTool("get_product", {
|
|
1080
1108
|
description: "Look up a product the store sells: name, plans and prices. Prices are in minor units (cents).",
|
|
@@ -1101,8 +1129,14 @@ function registerCheckoutTools(server, ctx) {
|
|
|
1101
1129
|
try {
|
|
1102
1130
|
return { cart: projectCart(await cope.reprice(creds.id)) };
|
|
1103
1131
|
} catch (error) {
|
|
1104
|
-
if (
|
|
1105
|
-
|
|
1132
|
+
if (hasCode(error, "cart_not_repriceable")) return { cart: projectCart(await cope.getCart(creds.id)) };
|
|
1133
|
+
if (hasCode(error, "missing_tax_location")) {
|
|
1134
|
+
return {
|
|
1135
|
+
cart: projectCart(await cope.getCart(creds.id)),
|
|
1136
|
+
totalsPending: "Totals need the buyer's country and postal code. Call set_buyer_identity when the buyer gives them."
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
throw error;
|
|
1106
1140
|
}
|
|
1107
1141
|
}));
|
|
1108
1142
|
server.registerTool("add_line", {
|
|
@@ -1215,17 +1249,19 @@ function registerCheckoutTools(server, ctx) {
|
|
|
1215
1249
|
return { cart: projectCart(cart) };
|
|
1216
1250
|
}));
|
|
1217
1251
|
server.registerTool("start_checkout", {
|
|
1218
|
-
description: "Create the checkout and return the URL where the buyer reviews,
|
|
1252
|
+
description: "Create the checkout and return the URL where the buyer reviews, gives the consents the cart needs and pays; buyerNext in the result says which. Calling it again returns the same checkout while it is open.",
|
|
1219
1253
|
inputSchema: z.object({ cartRef }),
|
|
1220
1254
|
annotations: { idempotentHint: true }
|
|
1221
1255
|
}, ({ cartRef: ref }) => run(async () => {
|
|
1222
1256
|
const { creds, cope } = await openCart(ref);
|
|
1223
1257
|
const payload = { success_url: ctx.checkout.successUrl, cancel_url: ctx.checkout.cancelUrl, consents: [] };
|
|
1224
|
-
|
|
1258
|
+
const { checkout, cart } = await startCheckout(cope, creds, payload);
|
|
1259
|
+
return projectCheckout(checkout, cart.lines);
|
|
1225
1260
|
}));
|
|
1226
1261
|
}
|
|
1227
1262
|
async function startCheckout(cope, creds, payload) {
|
|
1228
|
-
const
|
|
1263
|
+
const cart = await cope.getCart(creds.id);
|
|
1264
|
+
const inCheckout = cart.state === "checkout_in_progress";
|
|
1229
1265
|
let open = null;
|
|
1230
1266
|
for (let n = 0; n < MAX_CHECKOUT_KEYS; n++) {
|
|
1231
1267
|
let result;
|
|
@@ -1241,9 +1277,9 @@ async function startCheckout(cope, creds, payload) {
|
|
|
1241
1277
|
if (Date.parse(result.expiresAt) > Date.now()) open = result;
|
|
1242
1278
|
continue;
|
|
1243
1279
|
}
|
|
1244
|
-
if ((await cope.getCart(creds.id)).state !== "active") return result;
|
|
1280
|
+
if ((await cope.getCart(creds.id)).state !== "active") return { checkout: result, cart };
|
|
1245
1281
|
}
|
|
1246
|
-
if (open) return open;
|
|
1282
|
+
if (open) return { checkout: open, cart };
|
|
1247
1283
|
if (inCheckout) {
|
|
1248
1284
|
throw new CopeApiError(409, [{ code: "checkout_not_recoverable", message: "The open checkout expired or cannot be recovered" }]);
|
|
1249
1285
|
}
|
|
@@ -1265,8 +1301,11 @@ function hasCode(error, code) {
|
|
|
1265
1301
|
return error instanceof CopeApiError && error.code === code;
|
|
1266
1302
|
}
|
|
1267
1303
|
|
|
1304
|
+
// package.json
|
|
1305
|
+
var version = "0.1.1";
|
|
1306
|
+
|
|
1268
1307
|
// src/mcp/handler.ts
|
|
1269
|
-
var SERVER_INFO = { name: "cope-checkout", version
|
|
1308
|
+
var SERVER_INFO = { name: "cope-checkout", version };
|
|
1270
1309
|
function createCopeMcpHandler(options) {
|
|
1271
1310
|
if (options.allowedHosts.length === 0) throw new Error("allowedHosts must name at least one host");
|
|
1272
1311
|
parseCartRefKeys(options.cartRefKeys);
|