@payments-ai/mcp 2.4.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.http.js +498 -170
- package/package.json +1 -1
package/dist/main.http.js
CHANGED
|
@@ -247,17 +247,26 @@ var SUPPORTED_OAUTH_SCOPES = [
|
|
|
247
247
|
"docs:read",
|
|
248
248
|
"checkout:read",
|
|
249
249
|
"checkout:write",
|
|
250
|
-
"transaction:read"
|
|
250
|
+
"transaction:read",
|
|
251
|
+
"customer:read",
|
|
252
|
+
"invoice:read",
|
|
253
|
+
"webhook:listen"
|
|
251
254
|
];
|
|
255
|
+
var CLI_EMAIL_CREDENTIAL_SCOPES = [...SUPPORTED_OAUTH_SCOPES];
|
|
252
256
|
var OAUTH_CONSENT_READ_SCOPES = [
|
|
253
257
|
"merchant:read",
|
|
254
258
|
"product:read",
|
|
255
259
|
"docs:read",
|
|
256
260
|
"checkout:read",
|
|
257
|
-
"transaction:read"
|
|
261
|
+
"transaction:read",
|
|
262
|
+
"customer:read",
|
|
263
|
+
"invoice:read"
|
|
258
264
|
];
|
|
259
265
|
var EXPLICIT_GRANT_OAUTH_SCOPES = [
|
|
260
|
-
"transaction:read"
|
|
266
|
+
"transaction:read",
|
|
267
|
+
"customer:read",
|
|
268
|
+
"invoice:read",
|
|
269
|
+
"webhook:listen"
|
|
261
270
|
];
|
|
262
271
|
var EXPLICIT_GRANT_SCOPE_SET = new Set(EXPLICIT_GRANT_OAUTH_SCOPES);
|
|
263
272
|
function isExplicitGrantScope(scope) {
|
|
@@ -272,6 +281,7 @@ function buildOAuthAuthorizationServerMetadata(issuerUrl, options2) {
|
|
|
272
281
|
issuer: issuerUrl,
|
|
273
282
|
authorization_endpoint: `${issuerUrl}/api/v1/oauth/authorize`,
|
|
274
283
|
token_endpoint: `${issuerUrl}/api/v1/oauth/token`,
|
|
284
|
+
revocation_endpoint: `${issuerUrl}/api/v1/oauth/revoke`,
|
|
275
285
|
registration_endpoint: `${issuerUrl}/api/v1/oauth/register`,
|
|
276
286
|
scopes_supported: SUPPORTED_OAUTH_SCOPES,
|
|
277
287
|
response_types_supported: OAUTH_RESPONSE_TYPES_SUPPORTED,
|
|
@@ -287,6 +297,9 @@ var MCP_OAUTH_AUTHORIZATION_WINDOW_MINUTES = 30;
|
|
|
287
297
|
var MCP_OAUTH_AUTHORIZATION_WINDOW_MS = MCP_OAUTH_AUTHORIZATION_WINDOW_MINUTES * 60 * 1e3;
|
|
288
298
|
|
|
289
299
|
// ../../packages/mcp-contracts/dist/order-limits.js
|
|
300
|
+
var import_currency2 = __toESM(require_currency(), 1);
|
|
301
|
+
|
|
302
|
+
// ../../packages/mcp-contracts/dist/currency-precision.js
|
|
290
303
|
var import_currency = __toESM(require_currency(), 1);
|
|
291
304
|
|
|
292
305
|
// ../../packages/mcp-contracts/dist/plan-amount-limits.js
|
|
@@ -301,7 +314,8 @@ var MIN_PAID_PLAN_AMOUNT_BY_CURRENCY = {
|
|
|
301
314
|
dkk: 2.5,
|
|
302
315
|
nok: 3,
|
|
303
316
|
cad: 1,
|
|
304
|
-
aud: 1
|
|
317
|
+
aud: 1,
|
|
318
|
+
jpy: 50
|
|
305
319
|
};
|
|
306
320
|
var SUPPORTED_CURRENCIES = Object.freeze(Object.keys(MIN_PAID_PLAN_AMOUNT_BY_CURRENCY));
|
|
307
321
|
function isSupportedCurrency(currency5) {
|
|
@@ -320,15 +334,49 @@ function formatMinPaidPlanAmountMessage(currency5, minAmount) {
|
|
|
320
334
|
const code = currency5.trim().toUpperCase() || "USD";
|
|
321
335
|
return `Amount must be at least ${minAmount} ${code} for paid plans`;
|
|
322
336
|
}
|
|
337
|
+
function formatAmountPrecisionMessage(currency5, fractionDigits) {
|
|
338
|
+
const code = currency5.trim().toUpperCase();
|
|
339
|
+
if (fractionDigits === 0) {
|
|
340
|
+
return `Amount must be a whole number of ${code}, which has no minor unit. Recovery: round the amount to whole ${code}, e.g. 500 rather than 500.5.`;
|
|
341
|
+
}
|
|
342
|
+
return `Amount must have at most ${fractionDigits} decimal places in ${code}. Recovery: round the amount to ${fractionDigits} decimal places.`;
|
|
343
|
+
}
|
|
323
344
|
var LOWEST_MIN_PAID_PLAN_AMOUNT = Math.min(...Object.values(MIN_PAID_PLAN_AMOUNT_BY_CURRENCY));
|
|
324
345
|
var PAID_PLAN_NOT_FREE_BY_ZERO_AMOUNT = "A paid plan cannot be made free by setting its amount to 0; create a free-access plan instead.";
|
|
325
346
|
function formatAmountBelowLowestFloorMessage() {
|
|
326
347
|
const higherFloors = Object.entries(MIN_PAID_PLAN_AMOUNT_BY_CURRENCY).filter(([, minAmount]) => minAmount > LOWEST_MIN_PAID_PLAN_AMOUNT).map(([currency5, minAmount]) => `${minAmount} ${currency5.toUpperCase()}`).join(", ");
|
|
327
348
|
return `Amount must be at least ${LOWEST_MIN_PAID_PLAN_AMOUNT} for paid plans, and more in some currencies (${higherFloors}). Recovery: send an amount of at least the floor of the plan's currency, or 0 for a free-access plan. ${PAID_PLAN_NOT_FREE_BY_ZERO_AMOUNT}`;
|
|
328
349
|
}
|
|
329
|
-
var
|
|
330
|
-
|
|
331
|
-
|
|
350
|
+
var DEFAULT_MAX_PLAN_AMOUNT = 1e5;
|
|
351
|
+
var MAX_PLAN_AMOUNT_BY_CURRENCY = {
|
|
352
|
+
usd: DEFAULT_MAX_PLAN_AMOUNT,
|
|
353
|
+
eur: DEFAULT_MAX_PLAN_AMOUNT,
|
|
354
|
+
gbp: DEFAULT_MAX_PLAN_AMOUNT,
|
|
355
|
+
pln: DEFAULT_MAX_PLAN_AMOUNT,
|
|
356
|
+
chf: DEFAULT_MAX_PLAN_AMOUNT,
|
|
357
|
+
sek: DEFAULT_MAX_PLAN_AMOUNT,
|
|
358
|
+
dkk: DEFAULT_MAX_PLAN_AMOUNT,
|
|
359
|
+
nok: DEFAULT_MAX_PLAN_AMOUNT,
|
|
360
|
+
cad: DEFAULT_MAX_PLAN_AMOUNT,
|
|
361
|
+
aud: DEFAULT_MAX_PLAN_AMOUNT,
|
|
362
|
+
// Tech-lead decision (about the USD 100000 ceiling in yen), not a measured Whop limit.
|
|
363
|
+
jpy: 15e6
|
|
364
|
+
};
|
|
365
|
+
function getMaxPlanAmount(currency5) {
|
|
366
|
+
const key = currency5.trim().toLowerCase();
|
|
367
|
+
return MAX_PLAN_AMOUNT_BY_CURRENCY[key] ?? DEFAULT_MAX_PLAN_AMOUNT;
|
|
368
|
+
}
|
|
369
|
+
var HIGHEST_MAX_PLAN_AMOUNT = Math.max(...Object.values(MAX_PLAN_AMOUNT_BY_CURRENCY));
|
|
370
|
+
var MAX_AMOUNT_RECOVERY = "Recovery: lower the plan amount, or contact support if you need a higher per-plan limit.";
|
|
371
|
+
function formatMaxPlanAmountMessage(currency5) {
|
|
372
|
+
const code = currency5.trim().toUpperCase();
|
|
373
|
+
return `Amount must be at most ${getMaxPlanAmount(currency5)} ${code}. ${MAX_AMOUNT_RECOVERY}`;
|
|
374
|
+
}
|
|
375
|
+
function formatHigherMaxAmounts() {
|
|
376
|
+
return Object.entries(MAX_PLAN_AMOUNT_BY_CURRENCY).filter(([, maxAmount]) => maxAmount > DEFAULT_MAX_PLAN_AMOUNT).map(([currency5, maxAmount]) => `${maxAmount} ${currency5.toUpperCase()}`).join(", ");
|
|
377
|
+
}
|
|
378
|
+
function formatAmountAboveCeilingMessage() {
|
|
379
|
+
return `Amount must be at most ${DEFAULT_MAX_PLAN_AMOUNT}, or the higher ceiling of its currency (${formatHigherMaxAmounts()}). ${MAX_AMOUNT_RECOVERY}`;
|
|
332
380
|
}
|
|
333
381
|
var MAX_PERIOD_LENGTH_BY_BILLING_PERIOD = {
|
|
334
382
|
week: 52,
|
|
@@ -343,8 +391,52 @@ function formatPeriodLengthRecovery(maxLength) {
|
|
|
343
391
|
}
|
|
344
392
|
var MAX_FREE_TRIAL_DAYS = 730;
|
|
345
393
|
|
|
394
|
+
// ../../packages/mcp-contracts/dist/currency-precision.js
|
|
395
|
+
function currencyFractionDigits(currencyCode3) {
|
|
396
|
+
const code = currencyCode3.trim().toUpperCase();
|
|
397
|
+
let digits;
|
|
398
|
+
try {
|
|
399
|
+
digits = new Intl.NumberFormat("en-US", {
|
|
400
|
+
style: "currency",
|
|
401
|
+
currency: code
|
|
402
|
+
}).resolvedOptions().maximumFractionDigits;
|
|
403
|
+
} catch {
|
|
404
|
+
throw new Error(`Unsupported currency "${currencyCode3}"`);
|
|
405
|
+
}
|
|
406
|
+
if (typeof digits !== "number") {
|
|
407
|
+
throw new Error(`Unsupported currency "${currencyCode3}"`);
|
|
408
|
+
}
|
|
409
|
+
return digits;
|
|
410
|
+
}
|
|
411
|
+
var MINOR_UNIT_RELATIVE_TOLERANCE = Number.EPSILON * 16;
|
|
412
|
+
function amountExceedsCurrencyPrecision(amount, currencyCode3) {
|
|
413
|
+
const minorUnits = amount * 10 ** currencyFractionDigits(currencyCode3);
|
|
414
|
+
const drift = Math.abs(minorUnits - Math.round(minorUnits));
|
|
415
|
+
return drift > Math.abs(minorUnits) * MINOR_UNIT_RELATIVE_TOLERANCE;
|
|
416
|
+
}
|
|
417
|
+
function describeAmountPrecisionViolation(amount, currencyCode3) {
|
|
418
|
+
const fractionDigits = resolveFractionDigits(currencyCode3);
|
|
419
|
+
if (fractionDigits === void 0 || !amountExceedsCurrencyPrecision(amount, currencyCode3)) {
|
|
420
|
+
return void 0;
|
|
421
|
+
}
|
|
422
|
+
return formatAmountPrecisionMessage(currencyCode3, fractionDigits);
|
|
423
|
+
}
|
|
424
|
+
function describeRequestAmountPrecisionViolation(amount, currencyCode3) {
|
|
425
|
+
return describeAmountPrecisionViolation((0, import_currency.default)(amount).value, currencyCode3);
|
|
426
|
+
}
|
|
427
|
+
function resolveFractionDigits(currencyCode3) {
|
|
428
|
+
try {
|
|
429
|
+
return currencyFractionDigits(currencyCode3);
|
|
430
|
+
} catch {
|
|
431
|
+
return void 0;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
346
435
|
// ../../packages/mcp-contracts/dist/order-limits.js
|
|
347
|
-
|
|
436
|
+
function getMaxOrderAmount(currency5) {
|
|
437
|
+
return getMaxPlanAmount(currency5);
|
|
438
|
+
}
|
|
439
|
+
var HIGHEST_MAX_ORDER_AMOUNT = HIGHEST_MAX_PLAN_AMOUNT;
|
|
348
440
|
var MAX_ORDER_ITEM_QUANTITY = 1e4;
|
|
349
441
|
var MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD = {
|
|
350
442
|
day: 365,
|
|
@@ -358,7 +450,11 @@ function getMaxOrderPeriodLength(billingPeriod) {
|
|
|
358
450
|
var ORDER_PERIOD_CAPS = Object.entries(MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD).map(([period, max]) => `${max} ${period}`).join(" / ");
|
|
359
451
|
var ORDER_PERIOD_LENGTH_TOO_LONG_MESSAGE = `periodLength must be at most ${MAX_ORDER_PERIOD_LENGTH}, and at most one year of the chosen billingPeriod (${ORDER_PERIOD_CAPS}). Recovery: lower periodLength to the cap for your billingPeriod; nothing can bill less often than once a year.`;
|
|
360
452
|
var HIGHER_LIMIT_RECOVERY = "or contact support if you need a higher per-order limit.";
|
|
361
|
-
var
|
|
453
|
+
var UNIT_PRICE_RECOVERY = `Recovery: confirm the price with the user (amounts are in major units, so 19.99 means $19.99), ${HIGHER_LIMIT_RECOVERY}`;
|
|
454
|
+
var ORDER_UNIT_PRICE_TOO_HIGH_MESSAGE = `unitPrice must be at most ${DEFAULT_MAX_PLAN_AMOUNT}, or the higher ceiling of the order currency (${formatHigherMaxAmounts()}). ${UNIT_PRICE_RECOVERY}`;
|
|
455
|
+
function formatOrderUnitPriceTooHighMessage(currency5) {
|
|
456
|
+
return `unitPrice must be at most ${formatOrderCeiling(currency5)}. ${UNIT_PRICE_RECOVERY}`;
|
|
457
|
+
}
|
|
362
458
|
var ORDER_QUANTITY_TOO_HIGH_MESSAGE = `quantity must be at most ${MAX_ORDER_ITEM_QUANTITY}. Recovery: lower the quantity, or contact support if you need more units on one order line.`;
|
|
363
459
|
var ORDER_TRIAL_PERIOD_DAYS_TOO_LONG_MESSAGE = `trialPeriodDays must be at most ${MAX_FREE_TRIAL_DAYS} days. Recovery: lower the trial length.`;
|
|
364
460
|
var RECURRING_ORDER_ITEM_CADENCE_MESSAGE = 'billingPeriod and periodLength are required when type is "recurring". Recovery: send both, e.g. billingPeriod "month" with periodLength 1 to bill monthly, or billingPeriod "day" with periodLength 30 to bill every 30 days.';
|
|
@@ -368,48 +464,86 @@ var INTERVAL_DAYS_TOO_SHORT_MESSAGE = `intervalDays must be at least ${MIN_WHOP_
|
|
|
368
464
|
function isBelowMinWhopBillingPeriod(billingPeriod, periodLength) {
|
|
369
465
|
return billingPeriod === "day" && periodLength < MIN_WHOP_BILLING_PERIOD_DAYS;
|
|
370
466
|
}
|
|
371
|
-
function formatOrderLineTotalTooHighMessage(lineTotal2) {
|
|
372
|
-
return `unitPrice x quantity is ${formatOrderAmount(lineTotal2)}; one order line can charge at most ${
|
|
467
|
+
function formatOrderLineTotalTooHighMessage(lineTotal2, currency5) {
|
|
468
|
+
return `unitPrice x quantity is ${formatOrderAmount(lineTotal2, currency5)}; one order line can charge at most ${formatOrderCeiling(currency5)}. Recovery: lower the quantity or the unitPrice, ${HIGHER_LIMIT_RECOVERY}`;
|
|
373
469
|
}
|
|
374
|
-
function formatOrderTotalTooHighMessage(total, moment) {
|
|
375
|
-
return `This order charges ${formatOrderAmount(total)} ${moment}; one order can charge at most ${
|
|
470
|
+
function formatOrderTotalTooHighMessage(total, moment, currency5) {
|
|
471
|
+
return `This order charges ${formatOrderAmount(total, currency5)} ${moment}; one order can charge at most ${formatOrderCeiling(currency5)}. Recovery: remove items or lower quantities, ${HIGHER_LIMIT_RECOVERY}`;
|
|
376
472
|
}
|
|
377
473
|
function formatMaxOrderPeriodLengthMessage(billingPeriod, maxLength) {
|
|
378
474
|
return `periodLength must be at most ${maxLength} when billingPeriod is "${billingPeriod}" (an order renews at least once a year). ${formatPeriodLengthRecovery(maxLength)}`;
|
|
379
475
|
}
|
|
380
|
-
function
|
|
381
|
-
return (
|
|
476
|
+
function formatOrderCeiling(currencyCode3) {
|
|
477
|
+
return `${getMaxOrderAmount(currencyCode3)} ${currencyCode3.trim().toUpperCase()}`;
|
|
478
|
+
}
|
|
479
|
+
function formatOrderAmount(amount, currencyCode3) {
|
|
480
|
+
return (0, import_currency2.default)(amount, { precision: fractionDigitsOrTwo(currencyCode3) }).toString();
|
|
481
|
+
}
|
|
482
|
+
function fractionDigitsOrTwo(currencyCode3) {
|
|
483
|
+
try {
|
|
484
|
+
return currencyFractionDigits(currencyCode3);
|
|
485
|
+
} catch {
|
|
486
|
+
return 2;
|
|
487
|
+
}
|
|
382
488
|
}
|
|
383
489
|
|
|
384
490
|
// ../../packages/mcp-contracts/dist/order-item-limits.js
|
|
385
|
-
var
|
|
491
|
+
var import_currency3 = __toESM(require_currency(), 1);
|
|
386
492
|
var TRIAL_PERIOD_DAYS_NOT_RECURRING_MESSAGE = 'trialPeriodDays only applies to recurring custom items; a one_time line charges immediately. Recovery: remove trialPeriodDays, or use type "recurring" if the buyer should get a trial before the first charge.';
|
|
387
493
|
function checkCustomOrderItemLimits(item, ctx) {
|
|
388
494
|
checkRecurringCadence(item, ctx);
|
|
389
495
|
checkTrialPeriodDaysIsRecurring(item, ctx);
|
|
390
496
|
checkPeriodLengthCap(item, ctx);
|
|
391
497
|
checkDailyCadence(item, ctx);
|
|
392
|
-
checkLineTotal(item, ctx);
|
|
393
498
|
}
|
|
394
|
-
function
|
|
395
|
-
|
|
499
|
+
function checkCustomOrderItemAmounts(body, ctx) {
|
|
500
|
+
body.items.forEach((item, index) => {
|
|
501
|
+
if (!isPricedItem(item))
|
|
502
|
+
return;
|
|
503
|
+
const issue2 = describeLineAmountIssue(item, body.currency);
|
|
504
|
+
if (issue2 === void 0)
|
|
505
|
+
return;
|
|
506
|
+
ctx.addIssue({ code: "custom", message: issue2.message, path: ["items", index, issue2.field] });
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
function checkCustomOrderItemsTotal(body, ctx) {
|
|
510
|
+
const items = body.items.filter(isPricedItem);
|
|
511
|
+
if (items.some((item) => hasLineIssue(item, body.currency)))
|
|
512
|
+
return;
|
|
513
|
+
const total = items.reduce((sum, item) => sum.add(lineTotal(item)), (0, import_currency3.default)(0));
|
|
514
|
+
if (total.value <= getMaxOrderAmount(body.currency))
|
|
396
515
|
return;
|
|
397
516
|
ctx.addIssue({
|
|
398
517
|
code: "custom",
|
|
399
|
-
message:
|
|
400
|
-
path: ["
|
|
518
|
+
message: formatOrderTotalTooHighMessage(total.value, "today", body.currency),
|
|
519
|
+
path: ["items"]
|
|
401
520
|
});
|
|
402
521
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (
|
|
522
|
+
function hasLineIssue(item, currencyCode3) {
|
|
523
|
+
return exceedsFieldCeiling(item) || describeLineAmountIssue(item, currencyCode3) !== void 0;
|
|
524
|
+
}
|
|
525
|
+
function describeLineAmountIssue(item, currencyCode3) {
|
|
526
|
+
if (exceedsFieldCeiling(item))
|
|
527
|
+
return void 0;
|
|
528
|
+
const maxAmount = getMaxOrderAmount(currencyCode3);
|
|
529
|
+
if (item.unitPrice > maxAmount) {
|
|
530
|
+
return { field: "unitPrice", message: formatOrderUnitPriceTooHighMessage(currencyCode3) };
|
|
531
|
+
}
|
|
532
|
+
const precision = describeRequestAmountPrecisionViolation(item.unitPrice, currencyCode3);
|
|
533
|
+
if (precision !== void 0)
|
|
534
|
+
return { field: "unitPrice", message: precision };
|
|
535
|
+
const total = lineTotal(item).value;
|
|
536
|
+
if (total <= maxAmount)
|
|
537
|
+
return void 0;
|
|
538
|
+
return { field: "quantity", message: formatOrderLineTotalTooHighMessage(total, currencyCode3) };
|
|
539
|
+
}
|
|
540
|
+
function checkTrialPeriodDaysIsRecurring(item, ctx) {
|
|
541
|
+
if (item.type === "recurring" || item.trialPeriodDays === void 0)
|
|
408
542
|
return;
|
|
409
543
|
ctx.addIssue({
|
|
410
544
|
code: "custom",
|
|
411
|
-
message:
|
|
412
|
-
path: ["
|
|
545
|
+
message: TRIAL_PERIOD_DAYS_NOT_RECURRING_MESSAGE,
|
|
546
|
+
path: ["trialPeriodDays"]
|
|
413
547
|
});
|
|
414
548
|
}
|
|
415
549
|
function checkRecurringCadence(item, ctx) {
|
|
@@ -447,26 +581,14 @@ function checkDailyCadence(item, ctx) {
|
|
|
447
581
|
path: ["periodLength"]
|
|
448
582
|
});
|
|
449
583
|
}
|
|
450
|
-
function checkLineTotal(item, ctx) {
|
|
451
|
-
if (exceedsFieldCeiling(item))
|
|
452
|
-
return;
|
|
453
|
-
const total = lineTotal(item);
|
|
454
|
-
if (total.value <= MAX_ORDER_AMOUNT)
|
|
455
|
-
return;
|
|
456
|
-
ctx.addIssue({
|
|
457
|
-
code: "custom",
|
|
458
|
-
message: formatOrderLineTotalTooHighMessage(total.value),
|
|
459
|
-
path: ["quantity"]
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
584
|
function lineTotal(item) {
|
|
463
|
-
return (0,
|
|
585
|
+
return (0, import_currency3.default)(item.unitPrice).multiply(item.quantity);
|
|
464
586
|
}
|
|
465
587
|
function exceedsFieldCeiling(item) {
|
|
466
|
-
return item.unitPrice >
|
|
588
|
+
return item.unitPrice > HIGHEST_MAX_ORDER_AMOUNT || item.quantity > MAX_ORDER_ITEM_QUANTITY;
|
|
467
589
|
}
|
|
468
|
-
function
|
|
469
|
-
return
|
|
590
|
+
function isPricedItem(item) {
|
|
591
|
+
return typeof item === "object" && item !== null && "unitPrice" in item && typeof item.unitPrice === "number" && "quantity" in item && typeof item.quantity === "number";
|
|
470
592
|
}
|
|
471
593
|
|
|
472
594
|
// ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/external.js
|
|
@@ -11139,8 +11261,8 @@ var error43 = () => {
|
|
|
11139
11261
|
case "not_multiple_of":
|
|
11140
11262
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
11141
11263
|
case "unrecognized_keys": {
|
|
11142
|
-
const
|
|
11143
|
-
return `Chave${
|
|
11264
|
+
const plural3 = issue2.keys.length > 1 ? "s" : "";
|
|
11265
|
+
return `Chave${plural3} inv\xE1lida${plural3}: ${joinValues(issue2.keys, ", ")}`;
|
|
11144
11266
|
}
|
|
11145
11267
|
case "invalid_key":
|
|
11146
11268
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -11284,8 +11406,8 @@ var error44 = () => {
|
|
|
11284
11406
|
case "not_multiple_of":
|
|
11285
11407
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
11286
11408
|
case "unrecognized_keys": {
|
|
11287
|
-
const
|
|
11288
|
-
return `Chave${
|
|
11409
|
+
const plural3 = issue2.keys.length > 1 ? "s" : "";
|
|
11410
|
+
return `Chave${plural3} inv\xE1lida${plural3}: ${joinValues(issue2.keys, ", ")}`;
|
|
11289
11411
|
}
|
|
11290
11412
|
case "invalid_key":
|
|
11291
11413
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -20161,6 +20283,9 @@ var mcpPlanSummarySchema = external_exports.object({
|
|
|
20161
20283
|
// ../../packages/mcp-contracts/dist/create-order.js
|
|
20162
20284
|
var MAX_ORDER_ITEMS = 20;
|
|
20163
20285
|
var MAX_IDEMPOTENCY_KEY_LENGTH = 255;
|
|
20286
|
+
var MAX_ORDER_METADATA_KEYS = 50;
|
|
20287
|
+
var MAX_ORDER_METADATA_KEY_LENGTH = 40;
|
|
20288
|
+
var MAX_ORDER_METADATA_VALUE_LENGTH = 500;
|
|
20164
20289
|
var ORDER_ITEM_TYPES = ["one_time", "recurring"];
|
|
20165
20290
|
var ORDER_BILLING_PERIODS = ["day", "week", "month", "year"];
|
|
20166
20291
|
var orderQuantitySchema = external_exports.number().int().positive().max(MAX_ORDER_ITEM_QUANTITY, ORDER_QUANTITY_TOO_HIGH_MESSAGE);
|
|
@@ -20170,7 +20295,7 @@ var createOrderPlanItemSchema = external_exports.object({
|
|
|
20170
20295
|
}).strict();
|
|
20171
20296
|
var createOrderCustomItemSchema = external_exports.object({
|
|
20172
20297
|
label: external_exports.string().trim().min(1).max(255),
|
|
20173
|
-
unitPrice: external_exports.number().positive().max(
|
|
20298
|
+
unitPrice: external_exports.number().positive().max(HIGHEST_MAX_ORDER_AMOUNT, ORDER_UNIT_PRICE_TOO_HIGH_MESSAGE).describe("Price of one unit in major units of the order currency (decimal dollars, not cents): 10 means $10.00, 1999 means $1,999.00. A currency with no minor unit takes whole numbers: 500 means \xA5500."),
|
|
20174
20299
|
quantity: orderQuantitySchema,
|
|
20175
20300
|
type: external_exports.enum(ORDER_ITEM_TYPES),
|
|
20176
20301
|
billingPeriod: external_exports.enum(ORDER_BILLING_PERIODS).optional(),
|
|
@@ -20181,12 +20306,22 @@ var createOrderItemSchema = external_exports.union([
|
|
|
20181
20306
|
createOrderPlanItemSchema,
|
|
20182
20307
|
createOrderCustomItemSchema
|
|
20183
20308
|
]);
|
|
20309
|
+
var orderMetadataSchema = external_exports.record(external_exports.string().min(1, "metadata keys must not be empty.").max(MAX_ORDER_METADATA_KEY_LENGTH, `metadata keys must be at most ${MAX_ORDER_METADATA_KEY_LENGTH} characters. Recovery: send a shorter key.`), external_exports.string().max(MAX_ORDER_METADATA_VALUE_LENGTH, `metadata values must be at most ${MAX_ORDER_METADATA_VALUE_LENGTH} characters. Recovery: send a shorter value.`)).refine((metadata) => Object.keys(metadata).length <= MAX_ORDER_METADATA_KEYS, {
|
|
20310
|
+
message: `metadata must have at most ${MAX_ORDER_METADATA_KEYS} keys. Recovery: send fewer keys.`
|
|
20311
|
+
}).meta({
|
|
20312
|
+
maxProperties: MAX_ORDER_METADATA_KEYS,
|
|
20313
|
+
description: 'Your own key/value strings stored on the order and returned exactly as sent, for example { "cf_order_id": "A-1001" }. Not shown to the buyer.'
|
|
20314
|
+
});
|
|
20184
20315
|
var createOrderInputSchema = external_exports.object({
|
|
20185
20316
|
merchantId: external_exports.uuid(),
|
|
20186
20317
|
currency: supportedCurrencyCodeSchema,
|
|
20187
20318
|
items: external_exports.array(createOrderItemSchema).min(1).max(MAX_ORDER_ITEMS),
|
|
20188
|
-
idempotencyKey: external_exports.string().trim().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).optional()
|
|
20189
|
-
|
|
20319
|
+
idempotencyKey: external_exports.string().trim().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).optional(),
|
|
20320
|
+
metadata: orderMetadataSchema.optional()
|
|
20321
|
+
}).strict().superRefine((input2, ctx) => {
|
|
20322
|
+
checkCustomOrderItemAmounts(input2, ctx);
|
|
20323
|
+
checkCustomOrderItemsTotal(input2, ctx);
|
|
20324
|
+
});
|
|
20190
20325
|
var createOrderItemResponseSchema = external_exports.object({
|
|
20191
20326
|
label: external_exports.string(),
|
|
20192
20327
|
unitPrice: external_exports.number(),
|
|
@@ -20205,7 +20340,8 @@ var createOrderOutputSchema = external_exports.object({
|
|
|
20205
20340
|
billingPeriod: external_exports.enum(ORDER_BILLING_PERIODS).nullable(),
|
|
20206
20341
|
periodLength: external_exports.number().int().nullable(),
|
|
20207
20342
|
trialPeriodDays: external_exports.number().int().nullable(),
|
|
20208
|
-
items: external_exports.array(createOrderItemResponseSchema)
|
|
20343
|
+
items: external_exports.array(createOrderItemResponseSchema),
|
|
20344
|
+
metadata: external_exports.record(external_exports.string(), external_exports.string()).default({})
|
|
20209
20345
|
});
|
|
20210
20346
|
|
|
20211
20347
|
// ../../packages/mcp-contracts/dist/product-name.js
|
|
@@ -20241,13 +20377,13 @@ function formatPlanNameTooLong(length) {
|
|
|
20241
20377
|
function formatPlanDescriptionTooLong(length) {
|
|
20242
20378
|
return `Plan description is ${length} characters; max ${PLAN_DESCRIPTION_MAX}. Recovery: shorten the description to <=${PLAN_DESCRIPTION_MAX} characters \u2014 it is a one-line caption, so keep the pricing detail that distinguishes this plan \u2014 and move the full copy into this plan's richDescription (up to ${PLAN_RICH_DESCRIPTION_MAX} characters), which the hosted checkout page renders beneath it.`;
|
|
20243
20379
|
}
|
|
20244
|
-
var PLAN_AMOUNT_DESCRIPTION = "Price in major units of `currency` (decimal dollars, not cents): 19.99 means $19.99 and 1999 means $1,999.00. Stored as 0 on a free-access plan.";
|
|
20380
|
+
var PLAN_AMOUNT_DESCRIPTION = "Price in major units of `currency` (decimal dollars, not cents): 19.99 means $19.99 and 1999 means $1,999.00. A currency with no minor unit takes whole numbers: 500 means \xA5500. Stored as 0 on a free-access plan.";
|
|
20245
20381
|
var planShape = {
|
|
20246
20382
|
name: external_exports.string().trim().min(1).max(PLAN_NAME_MAX, tooLongError(formatPlanNameTooLong)),
|
|
20247
20383
|
description: external_exports.string().trim().max(PLAN_DESCRIPTION_MAX, tooLongError(formatPlanDescriptionTooLong)).optional(),
|
|
20248
20384
|
richDescription: external_exports.string().trim().max(PLAN_RICH_DESCRIPTION_MAX).optional(),
|
|
20249
20385
|
currency: supportedCurrencyCodeSchema,
|
|
20250
|
-
amount: external_exports.number().nonnegative().max(
|
|
20386
|
+
amount: external_exports.number().nonnegative().max(HIGHEST_MAX_PLAN_AMOUNT, formatAmountAboveCeilingMessage()).describe(PLAN_AMOUNT_DESCRIPTION),
|
|
20251
20387
|
type: external_exports.enum(PLAN_TYPES2),
|
|
20252
20388
|
billingPeriod: external_exports.enum(BILLING_PERIODS2),
|
|
20253
20389
|
periodLength: external_exports.number().int().positive(),
|
|
@@ -20265,6 +20401,8 @@ var createMcpPlanSchema = external_exports.strictObject(planShape, { error: unre
|
|
|
20265
20401
|
checkPlanPeriodLength(plan, ctx);
|
|
20266
20402
|
checkFreeTrialIsRecurring(plan, ctx);
|
|
20267
20403
|
checkPaidPlanMinimum(plan, ctx);
|
|
20404
|
+
checkPlanCeiling(plan, ctx);
|
|
20405
|
+
checkPaidPlanPrecision(plan, ctx);
|
|
20268
20406
|
});
|
|
20269
20407
|
function checkPlanPeriodLength(plan, ctx) {
|
|
20270
20408
|
const maxPeriodLength = MAX_PERIOD_LENGTH_BY_BILLING_PERIOD[plan.billingPeriod];
|
|
@@ -20297,6 +20435,24 @@ function checkPaidPlanMinimum(plan, ctx) {
|
|
|
20297
20435
|
path: ["amount"]
|
|
20298
20436
|
});
|
|
20299
20437
|
}
|
|
20438
|
+
function checkPlanCeiling(plan, ctx) {
|
|
20439
|
+
if (plan.amount > HIGHEST_MAX_PLAN_AMOUNT || plan.amount <= getMaxPlanAmount(plan.currency)) {
|
|
20440
|
+
return;
|
|
20441
|
+
}
|
|
20442
|
+
ctx.addIssue({
|
|
20443
|
+
code: "custom",
|
|
20444
|
+
message: formatMaxPlanAmountMessage(plan.currency),
|
|
20445
|
+
path: ["amount"]
|
|
20446
|
+
});
|
|
20447
|
+
}
|
|
20448
|
+
function checkPaidPlanPrecision(plan, ctx) {
|
|
20449
|
+
if (plan.type === "free-access")
|
|
20450
|
+
return;
|
|
20451
|
+
const message = describeRequestAmountPrecisionViolation(plan.amount, plan.currency);
|
|
20452
|
+
if (message === void 0)
|
|
20453
|
+
return;
|
|
20454
|
+
ctx.addIssue({ code: "custom", message, path: ["amount"] });
|
|
20455
|
+
}
|
|
20300
20456
|
var productBodyBaseSchema = external_exports.object({
|
|
20301
20457
|
name: external_exports.string().trim().min(1).max(PRODUCT_NAME_MAX, tooLongError(formatProductNameTooLong)).refine((name) => !containsMarkupTag(name), PRODUCT_NAME_MARKUP_MESSAGE),
|
|
20302
20458
|
description: external_exports.string().trim().min(1).max(PRODUCT_DESCRIPTION_MAX, tooLongError(formatProductDescriptionTooLong)).optional(),
|
|
@@ -20385,7 +20541,7 @@ var updatePlanFieldsSchema = external_exports.object({
|
|
|
20385
20541
|
description: external_exports.string().trim().max(PLAN_DESCRIPTION_MAX, tooLongError3(formatPlanDescriptionTooLong2)).optional(),
|
|
20386
20542
|
billingPeriod: external_exports.enum(BILLING_PERIODS3).optional(),
|
|
20387
20543
|
periodLength: external_exports.number().int().positive().optional(),
|
|
20388
|
-
amount: external_exports.number().nonnegative().max(
|
|
20544
|
+
amount: external_exports.number().nonnegative().max(HIGHEST_MAX_PLAN_AMOUNT, formatAmountAboveCeilingMessage()).refine((amount) => amount === 0 || amount >= LOWEST_MIN_PAID_PLAN_AMOUNT, {
|
|
20389
20545
|
error: formatAmountBelowLowestFloorMessage()
|
|
20390
20546
|
}).optional(),
|
|
20391
20547
|
freeTrial: external_exports.number().int().min(0).max(MAX_FREE_TRIAL_DAYS, `freeTrial must be at most ${MAX_FREE_TRIAL_DAYS} days. Recovery: lower the trial length.`).optional()
|
|
@@ -20435,6 +20591,9 @@ var updateMcpPlanBackendBodySchema = external_exports.object({
|
|
|
20435
20591
|
|
|
20436
20592
|
// ../../packages/mcp-contracts/dist/resource-url.js
|
|
20437
20593
|
function canonicalResourceUrl(value) {
|
|
20594
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
20595
|
+
return null;
|
|
20596
|
+
}
|
|
20438
20597
|
if (value.includes("#")) {
|
|
20439
20598
|
return null;
|
|
20440
20599
|
}
|
|
@@ -20446,7 +20605,7 @@ function canonicalResourceUrl(value) {
|
|
|
20446
20605
|
}
|
|
20447
20606
|
|
|
20448
20607
|
// ../../packages/mcp-contracts/dist/mcp-session-expired.js
|
|
20449
|
-
var MCP_SESSION_EXPIRED_MESSAGE = "MCP Session Expired.
|
|
20608
|
+
var MCP_SESSION_EXPIRED_MESSAGE = "MCP Session Expired. Re-approve the connection in your AI client, or generate a new token in Developer Tools \u2192 MCP Tokens if you connected with a token.";
|
|
20450
20609
|
|
|
20451
20610
|
// ../../packages/mcp-contracts/dist/list-products.js
|
|
20452
20611
|
var PLAN_TYPES3 = ["free-access", "one-time", "recurring"];
|
|
@@ -20553,13 +20712,28 @@ var mcpMerchantSummarySchema = external_exports.object({
|
|
|
20553
20712
|
name: external_exports.string()
|
|
20554
20713
|
});
|
|
20555
20714
|
|
|
20715
|
+
// ../../packages/mcp-contracts/dist/credential-expiry.js
|
|
20716
|
+
var CREDENTIAL_EXPIRY_WARNING_WINDOW_DAYS = 14;
|
|
20717
|
+
var CREDENTIAL_EXPIRY_WARNING_WINDOW_SECONDS = CREDENTIAL_EXPIRY_WARNING_WINDOW_DAYS * 24 * 60 * 60;
|
|
20718
|
+
var CREDENTIAL_EXPIRY_WARNING_META_KEY = "ai.payments/credentialExpiryWarning";
|
|
20719
|
+
var credentialExpiryWarningSchema = external_exports.object({
|
|
20720
|
+
credentialExpiresAt: external_exports.iso.datetime().describe("When the MCP credential stops working (ISO 8601, UTC)."),
|
|
20721
|
+
credentialExpiresInSeconds: external_exports.number().int().nonnegative().describe("Seconds of validity left on the MCP credential; 0 once it has expired."),
|
|
20722
|
+
reapproveUrl: external_exports.url().describe("Page that walks the user through re-approving this MCP connection."),
|
|
20723
|
+
message: external_exports.string().describe("One sentence to relay to the user, with the re-approve link.")
|
|
20724
|
+
});
|
|
20725
|
+
|
|
20556
20726
|
// ../../packages/mcp-contracts/dist/whoami.js
|
|
20557
20727
|
var mcpConnectionHostSchema = external_exports.enum(["sandbox", "live"]);
|
|
20558
20728
|
var mcpConnectionProofSchema = external_exports.object({
|
|
20559
20729
|
userId: external_exports.uuid(),
|
|
20560
20730
|
scopes: external_exports.array(external_exports.string()).nullable(),
|
|
20561
20731
|
host: mcpConnectionHostSchema.describe(`The class of MCP URL this client is connected to: "live" for the main MCP URL, "sandbox" for the sandbox-only MCP URL. Not the environment (dev, stage or prod) and not a merchant's deployment.`),
|
|
20562
|
-
endpoint: external_exports.url()
|
|
20732
|
+
endpoint: external_exports.url(),
|
|
20733
|
+
merchantIds: external_exports.array(external_exports.uuid()).describe("Ids of the merchants this credential is a member of, as returned when the credential was validated. Call get_my_merchant for names and per-deployment state; on the main MCP URL a merchant that exists on sandbox only is listed there, not here."),
|
|
20734
|
+
credentialExpiresAt: external_exports.iso.datetime().nullable().describe("When this MCP credential stops working (ISO 8601, UTC); null when unknown."),
|
|
20735
|
+
credentialExpiresInSeconds: external_exports.number().int().nonnegative().nullable().describe("Seconds of validity left on this MCP credential; null when the expiry is unknown."),
|
|
20736
|
+
credentialExpiryWarning: credentialExpiryWarningSchema.optional().describe("Present only when the credential expires within the warning window: tell the user and share the re-approve link.")
|
|
20563
20737
|
});
|
|
20564
20738
|
var whoamiOutputSchema = mcpConnectionProofSchema;
|
|
20565
20739
|
|
|
@@ -22873,6 +23047,12 @@ function parsePhoneNumber2(text, options2, metadata) {
|
|
|
22873
23047
|
}
|
|
22874
23048
|
}
|
|
22875
23049
|
|
|
23050
|
+
// ../../node_modules/.pnpm/libphonenumber-js@1.13.14/node_modules/libphonenumber-js/es6/parsePhoneNumber.js
|
|
23051
|
+
function parsePhoneNumber3() {
|
|
23052
|
+
var _normalizeArguments = normalizeArguments(arguments), text = _normalizeArguments.text, options2 = _normalizeArguments.options, metadata = _normalizeArguments.metadata;
|
|
23053
|
+
return parsePhoneNumber2(text, options2, metadata);
|
|
23054
|
+
}
|
|
23055
|
+
|
|
22876
23056
|
// ../../node_modules/.pnpm/libphonenumber-js@1.13.14/node_modules/libphonenumber-js/es6/isValidPhoneNumber.js
|
|
22877
23057
|
function _typeof7(o) {
|
|
22878
23058
|
"@babel/helpers - typeof";
|
|
@@ -22929,6 +23109,11 @@ function isValidPhoneNumber() {
|
|
|
22929
23109
|
return phoneNumber && phoneNumber.isValid() || false;
|
|
22930
23110
|
}
|
|
22931
23111
|
|
|
23112
|
+
// ../../node_modules/.pnpm/libphonenumber-js@1.13.14/node_modules/libphonenumber-js/min/exports/parsePhoneNumber.js
|
|
23113
|
+
function parsePhoneNumber4() {
|
|
23114
|
+
return withMetadataArgument(parsePhoneNumber3, arguments);
|
|
23115
|
+
}
|
|
23116
|
+
|
|
22932
23117
|
// ../../node_modules/.pnpm/libphonenumber-js@1.13.14/node_modules/libphonenumber-js/min/exports/isValidPhoneNumber.js
|
|
22933
23118
|
function isValidPhoneNumber2() {
|
|
22934
23119
|
return withMetadataArgument(isValidPhoneNumber, arguments);
|
|
@@ -22937,11 +23122,21 @@ function isValidPhoneNumber2() {
|
|
|
22937
23122
|
// ../../packages/mcp-contracts/dist/merchant-phone.js
|
|
22938
23123
|
var E164_PATTERN = /^\+[1-9]\d{1,14}$/;
|
|
22939
23124
|
var NANP_FICTIONAL_BLOCK = /^\+1\d{3}55501\d{2}$/;
|
|
23125
|
+
function isDialableWithPlusPrefix(phone) {
|
|
23126
|
+
return /^\d+$/.test(phone) && isDialablePhoneNumber(`+${phone}`);
|
|
23127
|
+
}
|
|
23128
|
+
function countryOfPlusPrefixedDigits(phone) {
|
|
23129
|
+
if (!isDialableWithPlusPrefix(phone))
|
|
23130
|
+
return void 0;
|
|
23131
|
+
return parsePhoneNumber4(`+${phone}`)?.country;
|
|
23132
|
+
}
|
|
22940
23133
|
function isDialablePhoneNumber(phone) {
|
|
22941
23134
|
return E164_PATTERN.test(phone) && isValidPhoneNumber2(phone) && !NANP_FICTIONAL_BLOCK.test(phone);
|
|
22942
23135
|
}
|
|
22943
23136
|
|
|
22944
23137
|
// ../../packages/mcp-contracts/dist/create-merchant-account.js
|
|
23138
|
+
var PHONE_INVALID_MESSAGE = "Provide the user's own mobile number, dialable and in E.164 format with country code (format only: +14155552671). Recovery: ask the user for their real mobile number and never invent a placeholder or a reserved fictional number (a +1 555 area code or a 555-01XX number) \u2014 the identity-verification link is sent to it by SMS after go_live. If the user does not want to share one, omit phone.";
|
|
23139
|
+
var phoneMissingPlusMessage = (phone, country) => `The phone is missing the leading + of E.164 format. With a + it would read +${phone}${country ? ` (${country})` : ""}, which is only right if the number already starts with the user's country code. Recovery: prefix + (for example +14155552671) and re-send only if the number already starts with the user's country code; otherwise ask the user for their number with its country code.`;
|
|
22945
23140
|
var createMerchantAccountInputSchema = external_exports.object({
|
|
22946
23141
|
name: external_exports.string().trim().min(1, {
|
|
22947
23142
|
message: "Provide a non-empty business or display name. Recovery: pass a non-empty string in the name field and re-send the tool call. Merchants are identified downstream by merchantId, not by name, so disambiguate look-alike names by merchantId instead of by picking a distinct name here."
|
|
@@ -22954,8 +23149,13 @@ var createMerchantAccountInputSchema = external_exports.object({
|
|
|
22954
23149
|
* it by SMS, so shape alone is not enough: an invented placeholder passes E.164 and the
|
|
22955
23150
|
* SMS silently goes nowhere.
|
|
22956
23151
|
*/
|
|
22957
|
-
phone: external_exports.string().trim().
|
|
22958
|
-
|
|
23152
|
+
phone: external_exports.string().trim().superRefine((phone, ctx) => {
|
|
23153
|
+
if (isDialablePhoneNumber(phone))
|
|
23154
|
+
return;
|
|
23155
|
+
ctx.addIssue({
|
|
23156
|
+
code: "custom",
|
|
23157
|
+
message: isDialableWithPlusPrefix(phone) ? phoneMissingPlusMessage(phone, countryOfPlusPrefixedDigits(phone)) : PHONE_INVALID_MESSAGE
|
|
23158
|
+
});
|
|
22959
23159
|
}).optional(),
|
|
22960
23160
|
/**
|
|
22961
23161
|
* Optional retry correlation id from a prior partial failure.
|
|
@@ -25282,13 +25482,18 @@ var SANDBOX_TEST_CARD_NOTE = "To pay a sandbox checkout link, use test card 4242
|
|
|
25282
25482
|
|
|
25283
25483
|
// src/tools/tool-descriptions.ts
|
|
25284
25484
|
var CREATE_MERCHANT_CONTACT_GUIDANCE = "Requires only name and email. The email must be the user's own real address, asked from the user or taken from the conversation: example or placeholder addresses (such as you@example.com) and addresses that cannot receive mail are rejected. Phone is optional. If you pass one it must be the user's own mobile number, taken from the conversation or asked from the user; never invent a placeholder or a fictional number (such as a 555 number), because after go_live the identity-verification link is sent to it by SMS and a number that cannot be dialled is rejected. Use E.164 with country code (format only: +14155552671). If the user does not share a phone, omit it and the link goes by email.";
|
|
25285
|
-
var CREATE_PRODUCT_PLAN_BOUNDS = `A plan \`amount\` is at most ${
|
|
25485
|
+
var CREATE_PRODUCT_PLAN_BOUNDS = `A plan \`amount\` is at most ${formatAmountCeilings()} in the plan currency, \`periodLength\` at most one year of the chosen billingPeriod (${formatPeriodCaps(MAX_PERIOD_LENGTH_BY_BILLING_PERIOD)}), and \`freeTrial\` at most ${MAX_FREE_TRIAL_DAYS} days and only on recurring plans. A plan accepts only its documented keys; an unknown key (such as freeTrialDays) is rejected, not ignored.`;
|
|
25486
|
+
function formatAmountCeilings() {
|
|
25487
|
+
const higher = Object.entries(MAX_PLAN_AMOUNT_BY_CURRENCY).filter(([, max]) => max > DEFAULT_MAX_PLAN_AMOUNT).map(([currency5, max]) => `${max} ${currency5.toUpperCase()}`);
|
|
25488
|
+
return higher.length > 0 ? `${DEFAULT_MAX_PLAN_AMOUNT} (${higher.join(", ")})` : `${DEFAULT_MAX_PLAN_AMOUNT}`;
|
|
25489
|
+
}
|
|
25286
25490
|
function formatPeriodCaps(caps) {
|
|
25287
25491
|
return Object.entries(caps).map(([period, max]) => `${max} ${period}`).join(" / ");
|
|
25288
25492
|
}
|
|
25289
|
-
var PRICE_UNITS = `a decimal dollar amount in the plan currency (major units, e.g. 9.99 for $9.99 \u2014 NOT cents, so 1999 means $1,999.00; currency is one of ${SUPPORTED_CURRENCIES.join(", ")})`;
|
|
25493
|
+
var PRICE_UNITS = `a decimal dollar amount in the plan currency (major units, e.g. 9.99 for $9.99 \u2014 NOT cents, so 1999 means $1,999.00; a currency with no minor unit takes whole numbers, so 500 means \xA5500 and 500.5 is rejected; currency is one of ${SUPPORTED_CURRENCIES.join(", ")})`;
|
|
25290
25494
|
var CREATE_PRODUCT_STORED_PLANS = "`plans` repeats what was stored for each plan, in `planIds` order: amount, currency, `price` formatted as the checkout shows it, and for recurring plans billingPeriod, periodLength and freeTrial \u2014 check `price` against what the user asked for. `warnings`, when present, flags an amount that looks like cents; relay it and, if the user meant the smaller price, fix it with update_plan before sharing the link.";
|
|
25291
|
-
var CREATE_ORDER_BOUNDS = `currency is one of ${SUPPORTED_CURRENCIES.join(", ")}. unitPrice is at most ${
|
|
25495
|
+
var CREATE_ORDER_BOUNDS = `currency is one of ${SUPPORTED_CURRENCIES.join(", ")}. unitPrice is at most ${formatAmountCeilings()} in the order currency, and so is each line (unitPrice x quantity) and the order total; quantity is at most ${MAX_ORDER_ITEM_QUANTITY}; periodLength is at most one year of the billingPeriod (${formatPeriodCaps(MAX_ORDER_PERIOD_LENGTH_BY_BILLING_PERIOD)}). When billingPeriod is "day", periodLength must be at least ${MIN_WHOP_BILLING_PERIOD_DAYS} (the payment provider rejects true daily subscriptions). trialPeriodDays is at most ${MAX_FREE_TRIAL_DAYS}, only on recurring custom lines, and must match the trial length of every other recurring line in the order (catalogue plan or custom).`;
|
|
25496
|
+
var CREATE_ORDER_METADATA = ` Optional metadata: your own flat map of string keys to string values (for example { "cf_order_id": "A-1001" }), at most ${MAX_ORDER_METADATA_KEYS} keys, each key 1-${MAX_ORDER_METADATA_KEY_LENGTH} characters, each value at most ${MAX_ORDER_METADATA_VALUE_LENGTH} characters. It is stored on the order and returned in metadata exactly as sent (keys are not case-converted), including on the payment and subscription webhooks for the order; the buyer does not see it. Repeating an idempotencyKey returns the original order, including its original metadata; metadata on the repeat is ignored.`;
|
|
25292
25497
|
var CREATE_PRODUCT_STATUS_NOTE = "Optional `status` ('draft' | 'active' | 'archived') defaults to 'active' \u2014 the product is created live and purchasable immediately via its checkout link, regardless of status. Pass `status: 'draft'` only when the end user explicitly asks for a draft or temporary/not-yet-ready product.";
|
|
25293
25498
|
var CREATE_PRODUCT_COPY_CAPS = `Product \`name\` (<=${PRODUCT_NAME_MAX} chars), product \`description\` (<=${PRODUCT_DESCRIPTION_MAX} chars) and plan \`description\` (<=${PLAN_DESCRIPTION_MAX} chars) are all short one-line copy \u2014 if yours is longer, shorten it before calling and put the full version in that plan's \`richDescription\` (<=${PLAN_RICH_DESCRIPTION_MAX} chars), which the hosted checkout page renders as plain text beneath the short description (newlines preserved, markup not interpreted). \`richDescription\` is per plan, so product-level copy that overflows must still be edited down.`;
|
|
25294
25499
|
var TRANSACTION_STATUS_RULE = 'Status: only "succeeded" means paid. "pending" is not yet confirmed by the payment provider, so never report a pending transaction as paid. "partially_refunded" means some but not all of the amount was refunded; "refunded", "failed" and "disputed" mean what they say.';
|
|
@@ -25297,14 +25502,14 @@ var TRANSACTION_SCOPE_RULE = 'Requires the "transaction:read" scope, which exist
|
|
|
25297
25502
|
var LIST_TRANSACTIONS_SUMMARY = `List a merchant's transactions, newest first, narrowed by the optional filters in the input schema. For the full lookup guide, call search_payments_ai_docs with "transaction lookup".`;
|
|
25298
25503
|
var GET_TRANSACTION_DETAIL = "Get one transaction by transactionId from list_transactions: the same fields plus the refunded amount and the order's line items. items is empty for a single-plan purchase, where plan names what was bought.";
|
|
25299
25504
|
var TOOL_DESCRIPTIONS = {
|
|
25300
|
-
[MCP_TOOL_NAMES.WHOAMI]: `Prove which account and which MCP server this connection reaches. Takes no arguments. Returns { userId, scopes, host, endpoint }, read from the validated token and the server that answered. "scopes" null means the token holds every scope except "transaction:read", which must be granted by name. "host" is the class of MCP URL this client is connected to: "live" for the main MCP URL, which serves every merchant at its own deployment, or "sandbox" for the sandbox-only MCP URL. It is not the environment (dev, stage or prod) and not a merchant's deployment: on a "live" host a merchant that has not gone live still reports deployment "sandbox" in get_my_merchant, which is expected. To prove the connection, quote userId, host and endpoint from this result rather than the client's connected label. Needs no scope: any valid token can call it. Call get_my_merchant next to find the merchants this connection owns
|
|
25505
|
+
[MCP_TOOL_NAMES.WHOAMI]: `Prove which account and which MCP server this connection reaches. Takes no arguments. Returns { userId, scopes, host, endpoint, merchantIds, credentialExpiresAt, credentialExpiresInSeconds, credentialExpiryWarning? }, read from the validated token and the server that answered. "scopes" null means the token holds every scope except "transaction:read", which must be granted by name. "host" is the class of MCP URL this client is connected to: "live" for the main MCP URL, which serves every merchant at its own deployment, or "sandbox" for the sandbox-only MCP URL. It is not the environment (dev, stage or prod) and not a merchant's deployment: on a "live" host a merchant that has not gone live still reports deployment "sandbox" in get_my_merchant, which is expected. To prove the connection, quote userId, host and endpoint from this result rather than the client's connected label. "merchantIds" are the merchants this credential is a member of; credentialExpiresAt and credentialExpiresInSeconds say when it stops working (null when unknown). credentialExpiryWarning is present only when the credential expires within ` + CREDENTIAL_EXPIRY_WARNING_WINDOW_DAYS + " days: tell the user and share its reapproveUrl, and expect the same warning appended to every other tool result until they re-approve. Needs no scope: any valid token can call it. Call get_my_merchant next to find the merchants this connection owns.",
|
|
25301
25506
|
[MCP_TOOL_NAMES.GET_MY_MERCHANT]: 'List the merchant accounts this connection already owns. Call this BEFORE create_merchant_account: a new agent session cannot remember a merchant created in an earlier one, and creating again produces a second merchant with its own products and its own checkout links. Takes no arguments \u2014 the merchants come from the authorization on the connection itself. Returns { merchants: [{ merchantId, name, deployment }] }. "deployment" is "sandbox" before go_live and "live" after it has succeeded, and it matches where create_product and list_products will route for that merchant right now. An empty merchants array is a normal result, not an error: this connection owns no merchant yet, so call create_merchant_account. When more than one merchant comes back, ask which one to use \u2014 do not pick for the user. Requires the "merchant:read" scope.',
|
|
25302
25507
|
[MCP_TOOL_NAMES.CREATE_MERCHANT_ACCOUNT]: "Create a merchant account on PAI Managed. Call get_my_merchant first: if it returns a merchant, reuse that merchantId instead of creating another, because a duplicate merchant gets its own products and checkout links. " + CREATE_MERCHANT_CONTACT_GUIDANCE + " The business name is a display label and is not globally unique \u2014 every downstream tool (create_product, list_products, checkout) keys on merchantId, so if two merchants happen to share a name, disambiguate them by merchantId (do not pick a distinct name here to work around it). No identity verification (KYC/KYB) is needed to use sandbox \u2014 products, checkout links and test transactions all work without it; verification is required on live. " + IDENTITY_VERIFICATION_RULE + " Dual-provisions live and sandbox with a shared merchantId. On partial dual-provision failure (one backend created it, the other did not), retry without a merchantId to finish it. When both backends fail, the returned error names the appropriate recovery \u2014 follow it as-is rather than repeating the same call in a loop, and treat repeated identical failures as needing escalation to support. Returns that merchant ID. Without a merchantId, a merchant this account already owns is returned with existing: true instead of a new one being created, and whichever of live or sandbox is missing it gets a copy with the details the existing copy holds; a merchantId this account already owns is finished the same way, and the copy that exists is never changed. Next: create_product (and checkout tools) on sandbox, then go_live when ready.",
|
|
25303
25508
|
[MCP_TOOL_NAMES.GET_MERCHANT_ACTIVATION_STATUS]: 'Get the production activation status of a merchant account on PAI Managed. Returns { status, identityVerification }. status "pending": the merchant has not been promoted to live yet; this is the only status where go_live is the next step, once the user confirms the merchant is ready, and polling this tool will not change it on its own. status "verification_pending": the merchant is promoted to live and identity verification (KYC) is not complete yet; do not call go_live again. status "active": live and identity verified. status "not_provisioned": no live merchant record exists (dual provision never finished on live); recovery: re-run create_merchant_account without a merchantId, which finds this merchant and creates the missing live copy with the details the sandbox copy holds \u2014 go_live cannot fix this and fails while the live twin is missing. ' + IDENTITY_VERIFICATION_RULE + ' identityVerification is the KYC state: "not_started", "pending", "in_review", "verified" or "rejected".',
|
|
25304
25509
|
[MCP_TOOL_NAMES.GO_LIVE]: 'IRREVERSIBLE. Promote a sandbox merchant to live by setting liveMerchantId (Merchant Go-Live). This is one-way: it assigns merchant-of-record liability and notifies KYC on the live backend, and there is no tool that undoes it. Confirm with the user before calling it. Validates deployment readiness (sandbox merchant exists, not linked to a different live id, live twin reachable), marks the sandbox merchant live with the dual-provision twin id, then best-effort notifies KYC on the live backend. Pass dryRun: true to run exactly those readiness checks and stop before anything is written \u2014 the response then carries dryRun: true, and its absence is your proof that a real promotion happened. Unknown arguments are rejected rather than ignored, so a guessed safety flag fails loudly instead of being silently dropped. Idempotent: a call for a merchant that is already live changes nothing and returns alreadyLive: true, so there is never a reason to call it twice. The response carries activationStatus, the same shape get_merchant_activation_status returns ("verification_pending" right after promotion until identity verification completes; omitted only when the live status cannot be read), and a real call also returns nextSteps to relay to the user. ' + LIVE_CATALOG_RULE + " " + IDENTITY_VERIFICATION_RULE + ' The identity-verification link goes by SMS to the merchant phone, or by email when there is no dialable phone; kycNotify.phoneIssue "invalid" means the phone number cannot be dialled, so tell the user it is invalid. Distinct from get_merchant_activation_status (activation and identity-verification status) and from ApiKeyEnvironment.',
|
|
25305
25510
|
[MCP_TOOL_NAMES.SEARCH_PAYMENTS_AI_DOCS]: "Search the PAI Managed documentation knowledge base to find guides, API references, UI walkthroughs, and feature explanations. Returns matching sections with titles and direct documentation links. Use this tool to understand how PAI Managed works, find step-by-step instructions for dashboard tasks, or locate specific features such as creating a merchant account (phone / E.164), go-live, creating products, configuring plans, or generating checkout links.",
|
|
25306
25511
|
[MCP_TOOL_NAMES.CREATE_PRODUCT]: "Create a product with one or more pricing plans for a merchant on the PAI Managed platform. Product and checkout mutations target sandbox until go_live. Products represent digital offerings (software, subscriptions, services) that customers can purchase via a checkout link. Each plan defines the price (" + PRICE_UNITS + "), billing type (one-time, recurring, or free-access), and billing period. " + CREATE_PRODUCT_PLAN_BOUNDS + " " + CREATE_PRODUCT_STATUS_NOTE + " " + CREATE_PRODUCT_COPY_CAPS + " Returns the product ID, the plan IDs, and ready-to-open hosted-checkout URLs: `checkoutUrl` for the first created plan and `planCheckoutUrls` (one `{planId, checkoutUrl}` entry per created plan) \u2014 do not construct them yourself. The URLs already reflect the merchant's go-live status, not your credentials: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded \u2014 nothing to switch on your side. " + CREATE_PRODUCT_STORED_PLANS + " Product images are not supported on create \u2014 after success, call get_product_image_upload_url with the returned id as productId, POST the file bytes (multipart/form-data) to the returned URL, then call confirm_product_image with the returned key.",
|
|
25307
|
-
[MCP_TOOL_NAMES.CREATE_ORDER]: "Create a payable order for a merchant on PAI Managed. Use create_order when the buyer pays for two or more items, or when you set the price yourself. Use the checkoutUrl from create_product or list_products for a single catalogue plan. Provide merchantId, currency (3 letters, for example usd), and items. A catalogue item is { planId, quantity }. A caller-priced item is { label, unitPrice, quantity, type } where type is one_time or recurring; a recurring caller-priced item also needs billingPeriod (day, week, month, or year) and periodLength. unitPrice is decimal dollars, not cents (10 means $10.00). " + CREATE_ORDER_BOUNDS + ' Optional idempotencyKey: repeating the same key returns the same orderId; omitting it creates a new order. Returns orderId and checkoutUrl \u2014 use the URL verbatim, do not construct it yourself. The URL already reflects the merchant\'s go-live status: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded. Requires the "checkout:write" scope.',
|
|
25512
|
+
[MCP_TOOL_NAMES.CREATE_ORDER]: "Create a payable order for a merchant on PAI Managed. Use create_order when the buyer pays for two or more items, or when you set the price yourself. Use the checkoutUrl from create_product or list_products for a single catalogue plan. Provide merchantId, currency (3 letters, for example usd), and items. A catalogue item is { planId, quantity }. A caller-priced item is { label, unitPrice, quantity, type } where type is one_time or recurring; a recurring caller-priced item also needs billingPeriod (day, week, month, or year) and periodLength. unitPrice is decimal dollars, not cents (10 means $10.00). " + CREATE_ORDER_BOUNDS + CREATE_ORDER_METADATA + ' Optional idempotencyKey: repeating the same key returns the same orderId; omitting it creates a new order. Returns orderId and checkoutUrl \u2014 use the URL verbatim, do not construct it yourself. The URL already reflects the merchant\'s go-live status: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded. Requires the "checkout:write" scope.',
|
|
25308
25513
|
[MCP_TOOL_NAMES.GET_PRODUCT_IMAGE_UPLOAD_URL]: 'Get a presigned POST policy to upload a product image directly to storage \u2014 the image bytes never pass through the tool call, and S3 enforces the 5 MB cap at upload time via `content-length-range` (oversized bodies get a 403 from S3, not the backend). Use this on any surface that can issue an HTTP multipart/form-data POST (CLI, agent, dashboard). The product must already exist (call create_product first). Provide merchantId, productId, fileName, and mimeType (image/png, image/jpeg, image/jpg, or image/webp). Returns { url, fields, key, expiresIn }: issue an HTTP POST to `url` with Content-Type: multipart/form-data. Add every entry from `fields` as a form field FIRST, then add the `file` field last (S3 requires the file field last), e.g. `curl -X POST <all -F fields...> -F "file=@./logo.png" "<url>"`. Then call confirm_product_image with the returned key to validate and attach the image.',
|
|
25309
25514
|
[MCP_TOOL_NAMES.CONFIRM_PRODUCT_IMAGE]: "Confirm a product image uploaded via a presigned POST policy from get_product_image_upload_url. Call this after the multipart/form-data POST succeeds. Provide merchantId, productId, and the key returned by get_product_image_upload_url. The backend validates the stored object (format, size, magic bytes) and attaches it to the product. Returns { imageUrl } for verification. Replaces any existing product image.",
|
|
25310
25515
|
[MCP_TOOL_NAMES.LIST_PRODUCTS]: "List products and pricing plans for a merchant on PAI Managed. Returns paginated products; each product carries a ready-to-open hosted-checkout `checkoutUrl`, and each plan summary (planId, name, amount, currency, `price` formatted as the checkout shows it, type, and for recurring plans billingPeriod, periodLength and freeTrial) carries its own `checkoutUrl` \u2014 use those directly, do not construct them yourself. The URLs already reflect the merchant's go-live status, not your credentials: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded \u2014 nothing to switch on your side. Optional filters: q (name search), status (draft|active|archived), type (one-time|recurring|free-access), dateRangeStart/dateRangeEnd, page, limit. Use before create_product when you need existing productId/planId values, or to share checkout links for plans that already exist. Targets sandbox until go_live, same as other product tools.",
|
|
@@ -25322,7 +25527,7 @@ var SOLO_TOOL_DESCRIPTION_OVERRIDES = {
|
|
|
25322
25527
|
[MCP_TOOL_NAMES.GET_MERCHANT_ACTIVATION_STATUS]: 'Get the production activation status of a merchant account on this sandbox MCP URL. Live promotion is not available on this URL, so the status you will typically see is "pending". Returns { status }. status "pending": the merchant has not been promoted to live; connect to the live MCP URL to promote it \u2014 polling this tool on the sandbox URL will not change it. status "verification_pending": the merchant is promoted to live and identity verification (KYC) is not confirmed; follow verification on the live MCP URL, which also reports the identity-verification state. status "active": live and identity verified; connect to the live MCP URL for live operations. status "not_provisioned": no merchant record exists on sandbox; recovery: connect to the live MCP URL and re-run create_merchant_account there without a merchantId, which creates the missing sandbox copy with the details the live copy holds.',
|
|
25323
25528
|
[MCP_TOOL_NAMES.SEARCH_PAYMENTS_AI_DOCS]: "Search the PAI Managed documentation knowledge base to find guides, API references, UI walkthroughs, and feature explanations. Returns matching sections with titles and direct documentation links. Use this tool to understand how PAI Managed works, find step-by-step instructions for dashboard tasks, or locate specific features such as creating a merchant account (phone / E.164), creating products, configuring plans, or generating checkout links. This sandbox MCP URL cannot call go_live; product and checkout writes stay in sandbox.",
|
|
25324
25529
|
[MCP_TOOL_NAMES.CREATE_PRODUCT]: "Create a product with one or more pricing plans for a merchant on Payments AI sandbox. Product and checkout mutations stay on sandbox on this MCP URL. Products represent digital offerings (software, subscriptions, services) that customers can purchase via a checkout link. Each plan defines the price (" + PRICE_UNITS + "), billing type (one-time, recurring, or free-access), and billing period. " + CREATE_PRODUCT_PLAN_BOUNDS + " " + CREATE_PRODUCT_STATUS_NOTE + " " + CREATE_PRODUCT_COPY_CAPS + " Returns the product ID, the plan IDs, and ready-to-open hosted-checkout URLs: `checkoutUrl` for the first created plan and `planCheckoutUrls` (one `{planId, checkoutUrl}` entry per created plan) \u2014 do not construct them yourself. The URLs are sandbox checkout links (`?isSandbox=true`). " + CREATE_PRODUCT_STORED_PLANS + " " + SANDBOX_TEST_CARD_NOTE + " Product images are not supported on create \u2014 after success, call get_product_image_upload_url with the returned id as productId, POST the file bytes (multipart/form-data) to the returned URL, then call confirm_product_image with the returned key.",
|
|
25325
|
-
[MCP_TOOL_NAMES.CREATE_ORDER]: "Create a payable order for a merchant on Payments AI sandbox. Use create_order when the buyer pays for two or more items, or when you set the price yourself. Use the checkoutUrl from create_product or list_products for a single catalogue plan. Provide merchantId, currency (3 letters, for example usd), and items. A catalogue item is { planId, quantity }. A caller-priced item is { label, unitPrice, quantity, type } where type is one_time or recurring; a recurring caller-priced item also needs billingPeriod (day, week, month, or year) and periodLength. unitPrice is decimal dollars, not cents (10 means $10.00). " + CREATE_ORDER_BOUNDS + ' Optional idempotencyKey: repeating the same key returns the same orderId; omitting it creates a new order. Returns orderId and checkoutUrl \u2014 use the URL verbatim, do not construct it yourself. The URL is a sandbox checkout link (`?isSandbox=true`). Requires the "checkout:write" scope. Stays on sandbox on this MCP URL. ' + SANDBOX_TEST_CARD_NOTE,
|
|
25530
|
+
[MCP_TOOL_NAMES.CREATE_ORDER]: "Create a payable order for a merchant on Payments AI sandbox. Use create_order when the buyer pays for two or more items, or when you set the price yourself. Use the checkoutUrl from create_product or list_products for a single catalogue plan. Provide merchantId, currency (3 letters, for example usd), and items. A catalogue item is { planId, quantity }. A caller-priced item is { label, unitPrice, quantity, type } where type is one_time or recurring; a recurring caller-priced item also needs billingPeriod (day, week, month, or year) and periodLength. unitPrice is decimal dollars, not cents (10 means $10.00). " + CREATE_ORDER_BOUNDS + CREATE_ORDER_METADATA + ' Optional idempotencyKey: repeating the same key returns the same orderId; omitting it creates a new order. Returns orderId and checkoutUrl \u2014 use the URL verbatim, do not construct it yourself. The URL is a sandbox checkout link (`?isSandbox=true`). Requires the "checkout:write" scope. Stays on sandbox on this MCP URL. ' + SANDBOX_TEST_CARD_NOTE,
|
|
25326
25531
|
[MCP_TOOL_NAMES.LIST_PRODUCTS]: "List products and pricing plans for a merchant on Payments AI sandbox. Returns paginated products; each product carries a ready-to-open hosted-checkout `checkoutUrl`, and each plan summary (planId, name, amount, currency, `price` formatted as the checkout shows it, type, and for recurring plans billingPeriod, periodLength and freeTrial) carries its own `checkoutUrl` \u2014 use those directly, do not construct them yourself. The URLs are sandbox checkout links (`?isSandbox=true`). Optional filters: q (name search), status (draft|active|archived), type (one-time|recurring|free-access), dateRangeStart/dateRangeEnd, page, limit. Use before create_product when you need existing productId/planId values, or to share checkout links for plans that already exist. Stays on sandbox on this MCP URL, same as other product tools.",
|
|
25327
25532
|
[MCP_TOOL_NAMES.LIST_TRANSACTIONS]: LIST_TRANSACTIONS_SUMMARY + ' This sandbox MCP URL returns sandbox transactions only; deployment "live" is refused. The top-level deployment names where it searched; an empty data list means no transaction matches there, not an error. ' + TRANSACTION_STATUS_RULE + " " + TRANSACTION_REFUND_RULE + " " + TRANSACTION_SCOPE_RULE,
|
|
25328
25533
|
[MCP_TOOL_NAMES.GET_TRANSACTION]: GET_TRANSACTION_DETAIL + ' This sandbox MCP URL returns sandbox transactions only; deployment "live" is refused. ' + TRANSACTION_STATUS_RULE + " " + TRANSACTION_REFUND_RULE + " " + TRANSACTION_SCOPE_RULE
|
|
@@ -25434,7 +25639,7 @@ var QUICKSTART_SKILL_SECTIONS = [
|
|
|
25434
25639
|
"`merchants` list means connected with no merchant yet. If the call is denied for",
|
|
25435
25640
|
"`merchant:read`, name that missing scope; do not say disconnected.",
|
|
25436
25641
|
"",
|
|
25437
|
-
"Ask for business **name
|
|
25642
|
+
"Ask for business **name** and **email**. **Phone** is optional: include it (the user's own mobile, E.164, e.g. `+14155552671`) only if they offer one; never invent a placeholder, and omit it otherwise. Call `create_merchant_account`. Show `merchantId`.",
|
|
25438
25643
|
"",
|
|
25439
25644
|
"### A2. Product",
|
|
25440
25645
|
"",
|
|
@@ -25448,15 +25653,9 @@ var QUICKSTART_SKILL_SECTIONS = [
|
|
|
25448
25653
|
"",
|
|
25449
25654
|
"### A3. Checkout URL",
|
|
25450
25655
|
"",
|
|
25451
|
-
"
|
|
25452
|
-
"",
|
|
25453
|
-
"`{{MANAGED_API_HOST_URL}}/payment/{planId}?isSandbox=true`",
|
|
25454
|
-
"",
|
|
25455
|
-
"Live (only after `go_live` **and** re-running `create_product` there \u2014 `go_live` does not create the plan on live, so this URL 404s until you do):",
|
|
25456
|
-
"",
|
|
25457
|
-
"`{{MANAGED_API_HOST_URL}}/payment/{planId}`",
|
|
25656
|
+
"Use the link the tool returns verbatim: `planCheckoutUrls[i].checkoutUrl` from `create_product` or `plans[i].checkoutUrl` from `list_products`. The top-level `checkoutUrl` is the first plan's link only. Never build a checkout URL from a `planId`.",
|
|
25458
25657
|
"",
|
|
25459
|
-
"
|
|
25658
|
+
"Sandbox and live are separate backends and `go_live` does not copy products or plans: sandbox plans do not exist on live. To get a live link, call `create_product` again after `go_live` and use the `checkoutUrl` it returns. A sandbox checkout link keeps `?isSandbox=true` forever and never becomes a live link.",
|
|
25460
25659
|
"",
|
|
25461
25660
|
"Test card (Sandbox): `4242 4242 4242 4242`, any future expiry, any CVC. Refused-payment card (Sandbox): `4000 0000 0000 0002`. Give both to the user with every sandbox checkout link. Test cards never work on Live: live checkout links take real cards only.",
|
|
25462
25661
|
"",
|
|
@@ -25481,24 +25680,26 @@ var QUICKSTART_SKILL_SECTIONS = [
|
|
|
25481
25680
|
"",
|
|
25482
25681
|
"## Tools",
|
|
25483
25682
|
"",
|
|
25484
|
-
"| Tool | Use
|
|
25485
|
-
"| -------------------------------- |
|
|
25486
|
-
"| `whoami` | `userId`, `scopes`, `host`, `endpoint`; any valid token
|
|
25487
|
-
"| `search_payments_ai_docs` | Search developer docs
|
|
25488
|
-
"| `get_my_merchant` | Owned merchants
|
|
25489
|
-
"| `create_merchant_account` | Dual-provision live + sandbox merchant
|
|
25490
|
-
"| `get_merchant_activation_status` | Activation status (see below)
|
|
25491
|
-
"| `go_live` | Promote to live; a repeat returns current status
|
|
25492
|
-
"| `create_product` | Product + plans (sandbox until go-live)
|
|
25493
|
-
"| `create_order` | Multi-item or caller-priced checkout
|
|
25494
|
-
"| `
|
|
25495
|
-
"| `
|
|
25496
|
-
"| `
|
|
25497
|
-
"| `
|
|
25498
|
-
"| `
|
|
25499
|
-
"| `
|
|
25500
|
-
"| `
|
|
25501
|
-
"| `
|
|
25683
|
+
"| Tool | Use |",
|
|
25684
|
+
"| -------------------------------- | ---------------------------------------------------------------------------- |",
|
|
25685
|
+
"| `whoami` | `userId`, `scopes`, `host`, `endpoint`; any valid token |",
|
|
25686
|
+
"| `search_payments_ai_docs` | Search developer docs |",
|
|
25687
|
+
"| `get_my_merchant` | Owned merchants |",
|
|
25688
|
+
"| `create_merchant_account` | Dual-provision live + sandbox merchant |",
|
|
25689
|
+
"| `get_merchant_activation_status` | Activation status (see below) |",
|
|
25690
|
+
"| `go_live` | Promote to live; a repeat returns current status |",
|
|
25691
|
+
"| `create_product` | Product + plans (sandbox until go-live) |",
|
|
25692
|
+
"| `create_order` | Multi-item or caller-priced checkout |",
|
|
25693
|
+
"| `update_product` | Merge-update a product's name, description or status |",
|
|
25694
|
+
"| `update_plan` | Merge-update a plan's name, description, price, billing period or free trial |",
|
|
25695
|
+
"| `list_products` | List products, plans, checkout URLs |",
|
|
25696
|
+
"| `list_transactions` | Look up transactions (needs `transaction:read`) |",
|
|
25697
|
+
"| `get_transaction` | One transaction's details and line items |",
|
|
25698
|
+
"| `get_product_image_upload_url` | Presigned POST for product image |",
|
|
25699
|
+
"| `confirm_product_image` | Attach uploaded product image |",
|
|
25700
|
+
"| `get_checkout_customization` | Read hosted checkout branding |",
|
|
25701
|
+
"| `update_checkout_customization` | Merge-update branding |",
|
|
25702
|
+
"| `set_checkout_logo` | Mint \u2192 PUT \u2192 confirm checkout logo |",
|
|
25502
25703
|
"",
|
|
25503
25704
|
"### Activation status",
|
|
25504
25705
|
"",
|
|
@@ -25566,6 +25767,115 @@ async function resolveTransactionLookupBackend(input2) {
|
|
|
25566
25767
|
return { error: NOT_LIVE_YET_ERROR };
|
|
25567
25768
|
}
|
|
25568
25769
|
|
|
25770
|
+
// src/auth/mcp-auth-rejection.ts
|
|
25771
|
+
var MCP_AUTH_ERROR_DOC_SLUG = {
|
|
25772
|
+
missing_token: "missing-token",
|
|
25773
|
+
invalid_token: "invalid-token",
|
|
25774
|
+
token_expired: "token-expired"
|
|
25775
|
+
};
|
|
25776
|
+
var DEPLOYMENT_LABEL = {
|
|
25777
|
+
sandbox: "Sandbox",
|
|
25778
|
+
live: "Live"
|
|
25779
|
+
};
|
|
25780
|
+
var MCP_TOKENS_DASHBOARD_PATH = "Developer Tools \u2192 MCP Tokens";
|
|
25781
|
+
var MESSAGE_BY_CODE = {
|
|
25782
|
+
missing_token: () => `No MCP credential was sent. Send an "Authorization: Bearer <token>" header with a pai_mcp_ token from ${MCP_TOKENS_DASHBOARD_PATH}, or connect with OAuth.`,
|
|
25783
|
+
invalid_token: (context) => {
|
|
25784
|
+
const here = DEPLOYMENT_LABEL[context.serverDeployment];
|
|
25785
|
+
return `This MCP token is not valid on this ${here} MCP URL: it is unknown, revoked or malformed. Tokens generated on the ${here} tab of ${MCP_TOKENS_DASHBOARD_PATH} work only with the ${here} MCP URL. Generate a new token there.`;
|
|
25786
|
+
},
|
|
25787
|
+
token_expired: (context) => `${context.expiredMessage ?? MCP_SESSION_EXPIRED_MESSAGE} How to reconnect: ${context.connectPageUrl}`,
|
|
25788
|
+
token_validation_unavailable: () => "The MCP token could not be validated because the backend is unavailable. Retry in a moment; do not generate a new token."
|
|
25789
|
+
};
|
|
25790
|
+
function resolveDocsBase(docsBaseUrl2) {
|
|
25791
|
+
return (docsBaseUrl2 ?? DEFAULT_DOCS_BASE_URL).replace(/\/+$/, "");
|
|
25792
|
+
}
|
|
25793
|
+
function buildMcpAuthErrorDocUrl(code, docsBaseUrl2) {
|
|
25794
|
+
const base = resolveDocsBase(docsBaseUrl2);
|
|
25795
|
+
const slug = MCP_AUTH_ERROR_DOC_SLUG[code];
|
|
25796
|
+
return slug === void 0 ? `${base}/errors` : `${base}/errors/${slug}`;
|
|
25797
|
+
}
|
|
25798
|
+
function buildMcpConnectPageUrl(docsBaseUrl2) {
|
|
25799
|
+
return `${resolveDocsBase(docsBaseUrl2)}/connect`;
|
|
25800
|
+
}
|
|
25801
|
+
function buildMcpConnectDiagnosticsUrl(mcpPath2, mcpPublicUrl2) {
|
|
25802
|
+
const endpoint = mcpPublicUrl2 === void 0 ? mcpPath2 : mcpPublicUrl2.replace(/\/+$/, "");
|
|
25803
|
+
return `${endpoint}${CONNECT_DIAGNOSTICS_PATH_SUFFIX}`;
|
|
25804
|
+
}
|
|
25805
|
+
function buildMcpAuthRejectionBody(code, context, links) {
|
|
25806
|
+
const connectPageUrl = buildMcpConnectPageUrl(links.docsBaseUrl);
|
|
25807
|
+
return {
|
|
25808
|
+
statusCode: 401,
|
|
25809
|
+
error: "Unauthorized",
|
|
25810
|
+
code,
|
|
25811
|
+
message: MESSAGE_BY_CODE[code]({ ...context, connectPageUrl }),
|
|
25812
|
+
docUrl: buildMcpAuthErrorDocUrl(code, links.docsBaseUrl),
|
|
25813
|
+
connectDiagnosticsUrl: links.connectDiagnosticsUrl,
|
|
25814
|
+
connectPageUrl
|
|
25815
|
+
};
|
|
25816
|
+
}
|
|
25817
|
+
function formatMcpAuthRejectionLog(rejection, mode) {
|
|
25818
|
+
return JSON.stringify({
|
|
25819
|
+
event: "mcp.auth_rejected",
|
|
25820
|
+
code: rejection.code,
|
|
25821
|
+
reason: rejection.reason,
|
|
25822
|
+
mode
|
|
25823
|
+
});
|
|
25824
|
+
}
|
|
25825
|
+
|
|
25826
|
+
// src/auth/credential-expiry.ts
|
|
25827
|
+
var SECONDS_PER_HOUR = 60 * 60;
|
|
25828
|
+
var SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR;
|
|
25829
|
+
var NO_EXPIRY = {
|
|
25830
|
+
credentialExpiresAt: null,
|
|
25831
|
+
credentialExpiresInSeconds: null,
|
|
25832
|
+
warning: void 0
|
|
25833
|
+
};
|
|
25834
|
+
function plural2(count, unit) {
|
|
25835
|
+
return `${count} ${unit}${count === 1 ? "" : "s"}`;
|
|
25836
|
+
}
|
|
25837
|
+
function describeRemaining(seconds) {
|
|
25838
|
+
if (seconds >= SECONDS_PER_DAY) {
|
|
25839
|
+
return plural2(Math.floor(seconds / SECONDS_PER_DAY), "day");
|
|
25840
|
+
}
|
|
25841
|
+
if (seconds >= SECONDS_PER_HOUR) {
|
|
25842
|
+
return plural2(Math.floor(seconds / SECONDS_PER_HOUR), "hour");
|
|
25843
|
+
}
|
|
25844
|
+
return "less than an hour";
|
|
25845
|
+
}
|
|
25846
|
+
function formatWarningMessage(expiresAt, seconds, reapproveUrl) {
|
|
25847
|
+
if (seconds === 0) {
|
|
25848
|
+
return `This MCP credential has expired (${expiresAt}). Tell the user to re-approve this connection so it works again: ${reapproveUrl}`;
|
|
25849
|
+
}
|
|
25850
|
+
return `This MCP credential expires in ${describeRemaining(seconds)} (${expiresAt}). Tell the user to re-approve this connection before then so it keeps working: ${reapproveUrl}`;
|
|
25851
|
+
}
|
|
25852
|
+
function resolveCredentialExpiry(input2) {
|
|
25853
|
+
const { expiresAt, now, docsBaseUrl: docsBaseUrl2, warnsBeforeExpiry } = input2;
|
|
25854
|
+
if (expiresAt === void 0) {
|
|
25855
|
+
return NO_EXPIRY;
|
|
25856
|
+
}
|
|
25857
|
+
const remainingMs = Date.parse(expiresAt) - now.getTime();
|
|
25858
|
+
const seconds = Math.max(0, Math.floor(remainingMs / 1e3));
|
|
25859
|
+
if (!warnsBeforeExpiry || seconds > CREDENTIAL_EXPIRY_WARNING_WINDOW_SECONDS) {
|
|
25860
|
+
return {
|
|
25861
|
+
credentialExpiresAt: expiresAt,
|
|
25862
|
+
credentialExpiresInSeconds: seconds,
|
|
25863
|
+
warning: void 0
|
|
25864
|
+
};
|
|
25865
|
+
}
|
|
25866
|
+
const reapproveUrl = buildMcpConnectPageUrl(docsBaseUrl2);
|
|
25867
|
+
return {
|
|
25868
|
+
credentialExpiresAt: expiresAt,
|
|
25869
|
+
credentialExpiresInSeconds: seconds,
|
|
25870
|
+
warning: {
|
|
25871
|
+
credentialExpiresAt: expiresAt,
|
|
25872
|
+
credentialExpiresInSeconds: seconds,
|
|
25873
|
+
reapproveUrl,
|
|
25874
|
+
message: formatWarningMessage(expiresAt, seconds, reapproveUrl)
|
|
25875
|
+
}
|
|
25876
|
+
};
|
|
25877
|
+
}
|
|
25878
|
+
|
|
25569
25879
|
// src/tools/tool-required-scopes.ts
|
|
25570
25880
|
var ANY_VALID_TOKEN = /* @__PURE__ */ Symbol("ANY_VALID_TOKEN");
|
|
25571
25881
|
var TOOL_REQUIRED_SCOPE = {
|
|
@@ -25638,6 +25948,36 @@ function filterToolsByScope(server, tokenPayload) {
|
|
|
25638
25948
|
});
|
|
25639
25949
|
}
|
|
25640
25950
|
|
|
25951
|
+
// src/tools/credential-expiry-warning.ts
|
|
25952
|
+
function hasContentBlocks(result2) {
|
|
25953
|
+
return typeof result2 === "object" && result2 !== null && Array.isArray(result2.content);
|
|
25954
|
+
}
|
|
25955
|
+
function withCredentialExpiryWarning(result2, warning) {
|
|
25956
|
+
if (!hasContentBlocks(result2)) {
|
|
25957
|
+
return result2;
|
|
25958
|
+
}
|
|
25959
|
+
return {
|
|
25960
|
+
...result2,
|
|
25961
|
+
content: [...result2.content, { type: "text", text: warning.message }],
|
|
25962
|
+
// eslint-disable-next-line no-underscore-dangle -- MCP names the result metadata field `_meta`
|
|
25963
|
+
_meta: { ...result2._meta, [CREDENTIAL_EXPIRY_WARNING_META_KEY]: warning }
|
|
25964
|
+
};
|
|
25965
|
+
}
|
|
25966
|
+
function attachCredentialExpiryWarning(server, warning) {
|
|
25967
|
+
if (!warning) {
|
|
25968
|
+
return;
|
|
25969
|
+
}
|
|
25970
|
+
const protocol = server.server;
|
|
25971
|
+
const callTool = protocol._getRequestHandler?.("tools/call");
|
|
25972
|
+
if (!callTool) {
|
|
25973
|
+
throw new Error("The SDK tools/call handler is not available; register tools before wrapping");
|
|
25974
|
+
}
|
|
25975
|
+
server.server.setRequestHandler(
|
|
25976
|
+
"tools/call",
|
|
25977
|
+
async (request, ctx) => withCredentialExpiryWarning(await callTool(request, ctx), warning)
|
|
25978
|
+
);
|
|
25979
|
+
}
|
|
25980
|
+
|
|
25641
25981
|
// src/tools/product/build-plan-checkout-url.ts
|
|
25642
25982
|
var CHECKOUT_SANDBOX_QUERY_PARAM = "isSandbox";
|
|
25643
25983
|
function buildPlanCheckoutUrl(params) {
|
|
@@ -25650,18 +25990,17 @@ function buildPlanCheckoutUrl(params) {
|
|
|
25650
25990
|
}
|
|
25651
25991
|
|
|
25652
25992
|
// src/tools/product/stored-plan-summary.ts
|
|
25653
|
-
var
|
|
25993
|
+
var import_currency5 = __toESM(require_currency(), 1);
|
|
25654
25994
|
|
|
25655
25995
|
// src/shared/format-price.ts
|
|
25656
|
-
var
|
|
25996
|
+
var import_currency4 = __toESM(require_currency(), 1);
|
|
25657
25997
|
function formatPrice(amount, currencyCode3) {
|
|
25658
|
-
const price = (0,
|
|
25998
|
+
const price = (0, import_currency4.default)(amount);
|
|
25659
25999
|
const code = currencyCode3.toUpperCase();
|
|
25660
26000
|
try {
|
|
25661
26001
|
return new Intl.NumberFormat("en-US", {
|
|
25662
26002
|
style: "currency",
|
|
25663
|
-
currency: code
|
|
25664
|
-
minimumFractionDigits: 2
|
|
26003
|
+
currency: code
|
|
25665
26004
|
}).format(price.value);
|
|
25666
26005
|
} catch {
|
|
25667
26006
|
return `${price.toString()} ${code}`;
|
|
@@ -25702,12 +26041,19 @@ function buildAmountWarnings(plans) {
|
|
|
25702
26041
|
);
|
|
25703
26042
|
}
|
|
25704
26043
|
function looksLikeCents(plan) {
|
|
25705
|
-
return plan.type !== "free-access" && Number.isInteger(plan.amount) && plan.amount >= CENTS_SUSPECT_FLOOR_MULTIPLE * getMinPaidPlanAmount(plan.currency);
|
|
26044
|
+
return plan.type !== "free-access" && hasMinorUnit(plan.currency) && Number.isInteger(plan.amount) && plan.amount >= CENTS_SUSPECT_FLOOR_MULTIPLE * getMinPaidPlanAmount(plan.currency);
|
|
25706
26045
|
}
|
|
25707
26046
|
function formatCentsWarning(plan, index) {
|
|
25708
|
-
const asCents = (0,
|
|
26047
|
+
const asCents = (0, import_currency5.default)(plan.amount).divide(100).value;
|
|
25709
26048
|
return `plans.${index} ("${plan.name}"): amount ${plan.amount} is stored as ${formatPrice(plan.amount, plan.currency)}, because amounts are in major units, not cents. If the user meant ${formatPrice(asCents, plan.currency)}, call update_plan with amount ${asCents} before sharing the checkout link; otherwise no action is needed.`;
|
|
25710
26049
|
}
|
|
26050
|
+
function hasMinorUnit(currencyCode3) {
|
|
26051
|
+
try {
|
|
26052
|
+
return currencyFractionDigits(currencyCode3) > 0;
|
|
26053
|
+
} catch {
|
|
26054
|
+
return true;
|
|
26055
|
+
}
|
|
26056
|
+
}
|
|
25711
26057
|
|
|
25712
26058
|
// src/tools/product/create-product.tool.ts
|
|
25713
26059
|
var backendCreatedProductShapeSchema = external_exports.object({
|
|
@@ -26324,6 +26670,8 @@ var ORDER_DOC_SECTIONS = [
|
|
|
26324
26670
|
|
|
26325
26671
|
**create_order** takes \`merchantId\`, \`currency\` (3 letters, for example usd), and \`items\`. A catalogue item is \`{ planId, quantity }\`. A caller-priced item is \`{ label, unitPrice, quantity, type }\` where \`type\` is \`one_time\` or \`recurring\`. A recurring caller-priced item also needs \`billingPeriod\` (\`day\`, \`week\`, \`month\`, or \`year\`) and \`periodLength\`. \`unitPrice\` is decimal dollars, not cents. Optional \`idempotencyKey\`: the same key returns the same \`orderId\`; omitting it creates a new order.
|
|
26326
26672
|
|
|
26673
|
+
Optional \`metadata\` stores your own references on the order, for example \`{ "cf_order_id": "A-1001" }\`: a flat map of string keys to string values, at most ${MAX_ORDER_METADATA_KEYS} keys, each key 1-${MAX_ORDER_METADATA_KEY_LENGTH} characters, each value at most ${MAX_ORDER_METADATA_VALUE_LENGTH} characters. Keys and values are kept and returned in \`metadata\` exactly as sent (\`cf_order_id\`, \`cfOrderId\` and \`Order-Ref\` stay distinct), and payment and subscription webhooks for the order carry the same \`metadata\`. The buyer does not see them. Repeating an idempotencyKey returns the original order, including its original metadata; metadata on the repeat is ignored. The Public API create-order endpoint takes the same \`metadata\`.
|
|
26674
|
+
|
|
26327
26675
|
The tool returns \`orderId\` and \`checkoutUrl\`. Use \`checkoutUrl\` verbatim \u2014 do not build one yourself. Sandbox URLs carry \`?isSandbox=true\`. The merchant's go-live status selects sandbox versus live. Requires the \`checkout:write\` scope.`
|
|
26328
26676
|
}
|
|
26329
26677
|
];
|
|
@@ -26474,7 +26822,7 @@ function runSearchPaymentsAiDocsHandler(query) {
|
|
|
26474
26822
|
// src/tools/connection/connection-proof.ts
|
|
26475
26823
|
var CONNECTION_PROOF_UNAVAILABLE_ERROR = "Could not prove this MCP connection: the server has no validated token or public MCP URL for this request. Contact Payments AI support.";
|
|
26476
26824
|
function buildConnectionProof(input2) {
|
|
26477
|
-
const { payload, host: host2, endpoint } = input2;
|
|
26825
|
+
const { payload, host: host2, endpoint, credentialExpiry } = input2;
|
|
26478
26826
|
if (!payload || !host2 || !endpoint) {
|
|
26479
26827
|
return void 0;
|
|
26480
26828
|
}
|
|
@@ -26482,7 +26830,11 @@ function buildConnectionProof(input2) {
|
|
|
26482
26830
|
userId: payload.userId,
|
|
26483
26831
|
scopes: payload.scopes,
|
|
26484
26832
|
host: host2,
|
|
26485
|
-
endpoint
|
|
26833
|
+
endpoint,
|
|
26834
|
+
merchantIds: payload.merchantIds,
|
|
26835
|
+
credentialExpiresAt: credentialExpiry.credentialExpiresAt,
|
|
26836
|
+
credentialExpiresInSeconds: credentialExpiry.credentialExpiresInSeconds,
|
|
26837
|
+
...credentialExpiry.warning ? { credentialExpiryWarning: credentialExpiry.warning } : {}
|
|
26486
26838
|
});
|
|
26487
26839
|
return parsed.success ? parsed.data : void 0;
|
|
26488
26840
|
}
|
|
@@ -26847,7 +27199,7 @@ function toolError(text) {
|
|
|
26847
27199
|
};
|
|
26848
27200
|
}
|
|
26849
27201
|
function resolveBackendClients(options2) {
|
|
26850
|
-
if (options2
|
|
27202
|
+
if (options2.backendClients) {
|
|
26851
27203
|
return {
|
|
26852
27204
|
sandbox: options2.backendClients.sandbox,
|
|
26853
27205
|
live: options2.backendClients.live
|
|
@@ -26870,12 +27222,18 @@ async function resolveProductCheckoutRoute(sandbox, live, merchantId) {
|
|
|
26870
27222
|
function registerTools(server, options2) {
|
|
26871
27223
|
const { sandbox: sandboxBackendClient, live: liveBackendClient } = resolveBackendClients(options2);
|
|
26872
27224
|
const dualBackendClients = sandboxBackendClient ? { sandbox: sandboxBackendClient, live: liveBackendClient } : void 0;
|
|
26873
|
-
const deploymentRouter2 = options2
|
|
26874
|
-
const serverDeployment = options2
|
|
26875
|
-
const mcpTokenPayload = options2
|
|
26876
|
-
const checkoutBaseUrl2 = options2
|
|
26877
|
-
const dashboardUrl = options2
|
|
27225
|
+
const deploymentRouter2 = options2.deploymentRouter ?? (dualBackendClients && hasLiveBackend(dualBackendClients) ? createDualRouter(dualBackendClients) : void 0);
|
|
27226
|
+
const serverDeployment = options2.serverDeployment ?? deploymentRouter2?.serverDeployment;
|
|
27227
|
+
const mcpTokenPayload = options2.mcpTokenPayload;
|
|
27228
|
+
const checkoutBaseUrl2 = options2.checkoutBaseUrl ?? resolveCheckoutBaseUrl();
|
|
27229
|
+
const dashboardUrl = options2.dashboardUrl;
|
|
26878
27230
|
const toolDescriptions = resolveToolDescriptions(deploymentRouter2?.mode ?? "dual");
|
|
27231
|
+
const credentialExpiry = resolveCredentialExpiry({
|
|
27232
|
+
expiresAt: mcpTokenPayload?.expiresAt,
|
|
27233
|
+
now: options2.now(),
|
|
27234
|
+
docsBaseUrl: options2.docsBaseUrl,
|
|
27235
|
+
warnsBeforeExpiry: mcpTokenPayload?.oauthIssued === true
|
|
27236
|
+
});
|
|
26879
27237
|
const denyForTool = (name) => scopeDenial(TOOL_REQUIRED_SCOPE[name], mcpTokenPayload, dashboardUrl);
|
|
26880
27238
|
async function resolveCallerMerchantIds(router, payload) {
|
|
26881
27239
|
const allowlist = await router.resolveMerchantAllowlist(payload);
|
|
@@ -26938,7 +27296,8 @@ function registerTools(server, options2) {
|
|
|
26938
27296
|
return runWhoamiHandler({
|
|
26939
27297
|
payload: mcpTokenPayload,
|
|
26940
27298
|
host: serverDeployment,
|
|
26941
|
-
endpoint: options2
|
|
27299
|
+
endpoint: options2.mcpPublicUrl,
|
|
27300
|
+
credentialExpiry
|
|
26942
27301
|
});
|
|
26943
27302
|
}
|
|
26944
27303
|
);
|
|
@@ -27548,6 +27907,7 @@ function registerTools(server, options2) {
|
|
|
27548
27907
|
}
|
|
27549
27908
|
);
|
|
27550
27909
|
filterToolsByScope(server, mcpTokenPayload);
|
|
27910
|
+
attachCredentialExpiryWarning(server, credentialExpiry.warning);
|
|
27551
27911
|
}
|
|
27552
27912
|
|
|
27553
27913
|
// src/http/connect-diagnostics/connect-diagnostics-intake.ts
|
|
@@ -27810,6 +28170,7 @@ var DEFAULT_ALLOWED_MCP_ORIGIN_HOSTNAMES = [
|
|
|
27810
28170
|
"grok.com",
|
|
27811
28171
|
"www.grok.com",
|
|
27812
28172
|
"x.ai",
|
|
28173
|
+
"antigravity.google",
|
|
27813
28174
|
"gemini.google.com",
|
|
27814
28175
|
"aistudio.google.com",
|
|
27815
28176
|
"cursor.com",
|
|
@@ -27886,17 +28247,27 @@ function parseMcpTokenPayload(parsed) {
|
|
|
27886
28247
|
if (typeof parsed !== "object" || parsed === null) {
|
|
27887
28248
|
return void 0;
|
|
27888
28249
|
}
|
|
27889
|
-
const { userId, cognitoSub, merchantIds, scopes } = parsed;
|
|
28250
|
+
const { userId, cognitoSub, merchantIds, scopes, expiresAt, oauthIssued } = parsed;
|
|
27890
28251
|
if (typeof userId !== "string" || typeof cognitoSub !== "string" || !isStringArray(merchantIds)) {
|
|
27891
28252
|
return void 0;
|
|
27892
28253
|
}
|
|
28254
|
+
const expiry = parseExpiresAt(expiresAt);
|
|
27893
28255
|
return {
|
|
27894
28256
|
userId,
|
|
27895
28257
|
cognitoSub,
|
|
27896
28258
|
merchantIds,
|
|
27897
|
-
scopes: Array.isArray(scopes) ? scopes : null
|
|
28259
|
+
scopes: Array.isArray(scopes) ? scopes : null,
|
|
28260
|
+
oauthIssued: oauthIssued === true,
|
|
28261
|
+
...expiry === void 0 ? {} : { expiresAt: expiry }
|
|
27898
28262
|
};
|
|
27899
28263
|
}
|
|
28264
|
+
function parseExpiresAt(value) {
|
|
28265
|
+
if (typeof value !== "string") {
|
|
28266
|
+
return void 0;
|
|
28267
|
+
}
|
|
28268
|
+
const epochMs = Date.parse(value);
|
|
28269
|
+
return Number.isNaN(epochMs) ? void 0 : new Date(epochMs).toISOString();
|
|
28270
|
+
}
|
|
27900
28271
|
|
|
27901
28272
|
// src/auth/mcp-token-audience.ts
|
|
27902
28273
|
function isTokenAudienceAccepted(audience, mcpPublicUrl2) {
|
|
@@ -27910,63 +28281,17 @@ function isTokenAudienceAccepted(audience, mcpPublicUrl2) {
|
|
|
27910
28281
|
return canonicalAudience !== null && canonicalAudience === canonicalResourceUrl(mcpPublicUrl2);
|
|
27911
28282
|
}
|
|
27912
28283
|
|
|
27913
|
-
// src/auth/mcp-auth-rejection.ts
|
|
27914
|
-
var MCP_AUTH_ERROR_DOC_SLUG = {
|
|
27915
|
-
missing_token: "missing-token",
|
|
27916
|
-
invalid_token: "invalid-token",
|
|
27917
|
-
token_expired: "token-expired"
|
|
27918
|
-
};
|
|
27919
|
-
var DEPLOYMENT_LABEL = {
|
|
27920
|
-
sandbox: "Sandbox",
|
|
27921
|
-
live: "Live"
|
|
27922
|
-
};
|
|
27923
|
-
var MCP_TOKENS_DASHBOARD_PATH = "Developer Tools \u2192 MCP Tokens";
|
|
27924
|
-
var MESSAGE_BY_CODE = {
|
|
27925
|
-
missing_token: () => `No MCP credential was sent. Send an "Authorization: Bearer <token>" header with a pai_mcp_ token from ${MCP_TOKENS_DASHBOARD_PATH}, or connect with OAuth.`,
|
|
27926
|
-
invalid_token: (context) => {
|
|
27927
|
-
const here = DEPLOYMENT_LABEL[context.serverDeployment];
|
|
27928
|
-
return `This MCP token is not valid on this ${here} MCP URL: it is unknown, revoked or malformed. Tokens generated on the ${here} tab of ${MCP_TOKENS_DASHBOARD_PATH} work only with the ${here} MCP URL. Generate a new token there.`;
|
|
27929
|
-
},
|
|
27930
|
-
token_expired: (context) => context.expiredMessage ?? MCP_SESSION_EXPIRED_MESSAGE,
|
|
27931
|
-
token_validation_unavailable: () => "The MCP token could not be validated because the backend is unavailable. Retry in a moment; do not generate a new token."
|
|
27932
|
-
};
|
|
27933
|
-
function resolveDocsBase(docsBaseUrl2) {
|
|
27934
|
-
return (docsBaseUrl2 ?? DEFAULT_DOCS_BASE_URL).replace(/\/+$/, "");
|
|
27935
|
-
}
|
|
27936
|
-
function buildMcpAuthErrorDocUrl(code, docsBaseUrl2) {
|
|
27937
|
-
const base = resolveDocsBase(docsBaseUrl2);
|
|
27938
|
-
const slug = MCP_AUTH_ERROR_DOC_SLUG[code];
|
|
27939
|
-
return slug === void 0 ? `${base}/errors` : `${base}/errors/${slug}`;
|
|
27940
|
-
}
|
|
27941
|
-
function buildMcpConnectPageUrl(docsBaseUrl2) {
|
|
27942
|
-
return `${resolveDocsBase(docsBaseUrl2)}/connect`;
|
|
27943
|
-
}
|
|
27944
|
-
function buildMcpConnectDiagnosticsUrl(mcpPath2, mcpPublicUrl2) {
|
|
27945
|
-
const endpoint = mcpPublicUrl2 === void 0 ? mcpPath2 : mcpPublicUrl2.replace(/\/+$/, "");
|
|
27946
|
-
return `${endpoint}${CONNECT_DIAGNOSTICS_PATH_SUFFIX}`;
|
|
27947
|
-
}
|
|
27948
|
-
function buildMcpAuthRejectionBody(code, context, links) {
|
|
27949
|
-
return {
|
|
27950
|
-
statusCode: 401,
|
|
27951
|
-
error: "Unauthorized",
|
|
27952
|
-
code,
|
|
27953
|
-
message: MESSAGE_BY_CODE[code](context),
|
|
27954
|
-
docUrl: buildMcpAuthErrorDocUrl(code, links.docsBaseUrl),
|
|
27955
|
-
connectDiagnosticsUrl: links.connectDiagnosticsUrl,
|
|
27956
|
-
connectPageUrl: buildMcpConnectPageUrl(links.docsBaseUrl)
|
|
27957
|
-
};
|
|
27958
|
-
}
|
|
27959
|
-
function formatMcpAuthRejectionLog(code, mode) {
|
|
27960
|
-
return JSON.stringify({ event: "mcp.auth_rejected", code, mode });
|
|
27961
|
-
}
|
|
27962
|
-
|
|
27963
28284
|
// src/http/app.ts
|
|
27964
28285
|
var LOCALHOST_BINDS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
|
|
27965
28286
|
var LOCALHOST_ALLOWED_HOSTS = ["127.0.0.1", "localhost", "::1", "[::1]"];
|
|
27966
28287
|
var BEARER_PREFIX = "Bearer ";
|
|
27967
28288
|
var MCP_TOKEN_FORMAT = /^pai_mcp_[0-9a-f]{64}$/;
|
|
27968
|
-
function reject(code,
|
|
27969
|
-
return {
|
|
28289
|
+
function reject(code, options2 = {}) {
|
|
28290
|
+
return {
|
|
28291
|
+
ok: false,
|
|
28292
|
+
rejection: { code, reason: options2.reason ?? code },
|
|
28293
|
+
expiredMessage: options2.expiredMessage
|
|
28294
|
+
};
|
|
27970
28295
|
}
|
|
27971
28296
|
function decideFromOutcome(token, outcome, mcpPublicUrl2) {
|
|
27972
28297
|
if (outcome.ok) {
|
|
@@ -27974,7 +28299,7 @@ function decideFromOutcome(token, outcome, mcpPublicUrl2) {
|
|
|
27974
28299
|
}
|
|
27975
28300
|
switch (outcome.reason) {
|
|
27976
28301
|
case "expired":
|
|
27977
|
-
return reject("token_expired", outcome.expiredMessage);
|
|
28302
|
+
return reject("token_expired", { expiredMessage: outcome.expiredMessage });
|
|
27978
28303
|
case "unavailable":
|
|
27979
28304
|
return reject("token_validation_unavailable");
|
|
27980
28305
|
case "not_found":
|
|
@@ -27996,7 +28321,7 @@ async function authenticateMcpRequest(authHeader, validationClient, mcpPublicUrl
|
|
|
27996
28321
|
}
|
|
27997
28322
|
const token = authHeader.slice(BEARER_PREFIX.length);
|
|
27998
28323
|
if (!MCP_TOKEN_FORMAT.test(token)) {
|
|
27999
|
-
return reject("invalid_token");
|
|
28324
|
+
return reject("invalid_token", { reason: "malformed_credential" });
|
|
28000
28325
|
}
|
|
28001
28326
|
const outcome = await validateMcpToken(token, validationClient);
|
|
28002
28327
|
return decideFromOutcome(token, outcome, mcpPublicUrl2);
|
|
@@ -28026,7 +28351,8 @@ function createExpressApp(options2) {
|
|
|
28026
28351
|
websiteUrl,
|
|
28027
28352
|
checkoutBaseUrl: checkoutBaseUrl2,
|
|
28028
28353
|
allowedMcpOriginHostnames = DEFAULT_ALLOWED_MCP_ORIGIN_HOSTNAMES,
|
|
28029
|
-
deploymentRouter: deploymentRouterOption
|
|
28354
|
+
deploymentRouter: deploymentRouterOption,
|
|
28355
|
+
now = () => /* @__PURE__ */ new Date()
|
|
28030
28356
|
} = options2;
|
|
28031
28357
|
const deploymentRouter2 = deploymentRouterOption ?? (backendClients2 && hasLiveBackend(backendClients2) ? createDualRouter(backendClients2) : void 0);
|
|
28032
28358
|
const tokenValidationClient = deploymentRouter2?.tokenValidationClient ?? backendClients2?.live ?? backendClients2?.sandbox;
|
|
@@ -28103,17 +28429,17 @@ function createExpressApp(options2) {
|
|
|
28103
28429
|
const challengeScope = challengeOAuthScopes(deploymentRouter2).join(" ");
|
|
28104
28430
|
const mcpOriginMiddleware = createMcpOriginMiddleware(allowedMcpOriginHostnames);
|
|
28105
28431
|
const connectDiagnosticsUrl = buildMcpConnectDiagnosticsUrl(mcpPath2, mcpPublicUrl2);
|
|
28106
|
-
const sendUnauthorized = (res,
|
|
28432
|
+
const sendUnauthorized = (res, rejection, expiredMessage) => {
|
|
28107
28433
|
if (resourceMetadataUrl) {
|
|
28108
28434
|
res.set(
|
|
28109
28435
|
"WWW-Authenticate",
|
|
28110
28436
|
`Bearer realm="PAI Managed MCP", resource_metadata="${resourceMetadataUrl}", scope="${challengeScope}"`
|
|
28111
28437
|
);
|
|
28112
28438
|
}
|
|
28113
|
-
console.error(formatMcpAuthRejectionLog(
|
|
28439
|
+
console.error(formatMcpAuthRejectionLog(rejection, routerMode));
|
|
28114
28440
|
res.status(401).json(
|
|
28115
28441
|
buildMcpAuthRejectionBody(
|
|
28116
|
-
code,
|
|
28442
|
+
rejection.code,
|
|
28117
28443
|
{ serverDeployment, expiredMessage },
|
|
28118
28444
|
{ docsBaseUrl: docsBaseUrl2, connectDiagnosticsUrl }
|
|
28119
28445
|
)
|
|
@@ -28133,7 +28459,7 @@ function createExpressApp(options2) {
|
|
|
28133
28459
|
return;
|
|
28134
28460
|
}
|
|
28135
28461
|
if (!decision.ok) {
|
|
28136
|
-
sendUnauthorized(res, decision.
|
|
28462
|
+
sendUnauthorized(res, decision.rejection, decision.expiredMessage);
|
|
28137
28463
|
return;
|
|
28138
28464
|
}
|
|
28139
28465
|
req.mcpTokenPayload = decision.payload;
|
|
@@ -28171,7 +28497,9 @@ function createExpressApp(options2) {
|
|
|
28171
28497
|
checkoutBaseUrl: checkoutBaseUrl2,
|
|
28172
28498
|
dashboardUrl: websiteUrl,
|
|
28173
28499
|
mcpPublicUrl: mcpPublicUrl2,
|
|
28174
|
-
serverDeployment
|
|
28500
|
+
serverDeployment,
|
|
28501
|
+
docsBaseUrl: docsBaseUrl2,
|
|
28502
|
+
now
|
|
28175
28503
|
});
|
|
28176
28504
|
registerPrompts(server, {
|
|
28177
28505
|
servesGoLive: servesGoLive(deploymentRouter2),
|