@payments-ai/mcp 2.5.0 → 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 +285 -113
- package/package.json +1 -1
package/dist/main.http.js
CHANGED
|
@@ -249,8 +249,10 @@ var SUPPORTED_OAUTH_SCOPES = [
|
|
|
249
249
|
"checkout:write",
|
|
250
250
|
"transaction:read",
|
|
251
251
|
"customer:read",
|
|
252
|
-
"invoice:read"
|
|
252
|
+
"invoice:read",
|
|
253
|
+
"webhook:listen"
|
|
253
254
|
];
|
|
255
|
+
var CLI_EMAIL_CREDENTIAL_SCOPES = [...SUPPORTED_OAUTH_SCOPES];
|
|
254
256
|
var OAUTH_CONSENT_READ_SCOPES = [
|
|
255
257
|
"merchant:read",
|
|
256
258
|
"product:read",
|
|
@@ -263,7 +265,8 @@ var OAUTH_CONSENT_READ_SCOPES = [
|
|
|
263
265
|
var EXPLICIT_GRANT_OAUTH_SCOPES = [
|
|
264
266
|
"transaction:read",
|
|
265
267
|
"customer:read",
|
|
266
|
-
"invoice:read"
|
|
268
|
+
"invoice:read",
|
|
269
|
+
"webhook:listen"
|
|
267
270
|
];
|
|
268
271
|
var EXPLICIT_GRANT_SCOPE_SET = new Set(EXPLICIT_GRANT_OAUTH_SCOPES);
|
|
269
272
|
function isExplicitGrantScope(scope) {
|
|
@@ -11258,8 +11261,8 @@ var error43 = () => {
|
|
|
11258
11261
|
case "not_multiple_of":
|
|
11259
11262
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
11260
11263
|
case "unrecognized_keys": {
|
|
11261
|
-
const
|
|
11262
|
-
return `Chave${
|
|
11264
|
+
const plural3 = issue2.keys.length > 1 ? "s" : "";
|
|
11265
|
+
return `Chave${plural3} inv\xE1lida${plural3}: ${joinValues(issue2.keys, ", ")}`;
|
|
11263
11266
|
}
|
|
11264
11267
|
case "invalid_key":
|
|
11265
11268
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -11403,8 +11406,8 @@ var error44 = () => {
|
|
|
11403
11406
|
case "not_multiple_of":
|
|
11404
11407
|
return `N\xFAmero inv\xE1lido: deve ser m\xFAltiplo de ${issue2.divisor}`;
|
|
11405
11408
|
case "unrecognized_keys": {
|
|
11406
|
-
const
|
|
11407
|
-
return `Chave${
|
|
11409
|
+
const plural3 = issue2.keys.length > 1 ? "s" : "";
|
|
11410
|
+
return `Chave${plural3} inv\xE1lida${plural3}: ${joinValues(issue2.keys, ", ")}`;
|
|
11408
11411
|
}
|
|
11409
11412
|
case "invalid_key":
|
|
11410
11413
|
return `Entrada inv\xE1lida n${translateOriginWithArticle(issue2.origin, "definite")}`;
|
|
@@ -20280,6 +20283,9 @@ var mcpPlanSummarySchema = external_exports.object({
|
|
|
20280
20283
|
// ../../packages/mcp-contracts/dist/create-order.js
|
|
20281
20284
|
var MAX_ORDER_ITEMS = 20;
|
|
20282
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;
|
|
20283
20289
|
var ORDER_ITEM_TYPES = ["one_time", "recurring"];
|
|
20284
20290
|
var ORDER_BILLING_PERIODS = ["day", "week", "month", "year"];
|
|
20285
20291
|
var orderQuantitySchema = external_exports.number().int().positive().max(MAX_ORDER_ITEM_QUANTITY, ORDER_QUANTITY_TOO_HIGH_MESSAGE);
|
|
@@ -20300,11 +20306,18 @@ var createOrderItemSchema = external_exports.union([
|
|
|
20300
20306
|
createOrderPlanItemSchema,
|
|
20301
20307
|
createOrderCustomItemSchema
|
|
20302
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
|
+
});
|
|
20303
20315
|
var createOrderInputSchema = external_exports.object({
|
|
20304
20316
|
merchantId: external_exports.uuid(),
|
|
20305
20317
|
currency: supportedCurrencyCodeSchema,
|
|
20306
20318
|
items: external_exports.array(createOrderItemSchema).min(1).max(MAX_ORDER_ITEMS),
|
|
20307
|
-
idempotencyKey: external_exports.string().trim().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).optional()
|
|
20319
|
+
idempotencyKey: external_exports.string().trim().min(1).max(MAX_IDEMPOTENCY_KEY_LENGTH).optional(),
|
|
20320
|
+
metadata: orderMetadataSchema.optional()
|
|
20308
20321
|
}).strict().superRefine((input2, ctx) => {
|
|
20309
20322
|
checkCustomOrderItemAmounts(input2, ctx);
|
|
20310
20323
|
checkCustomOrderItemsTotal(input2, ctx);
|
|
@@ -20327,7 +20340,8 @@ var createOrderOutputSchema = external_exports.object({
|
|
|
20327
20340
|
billingPeriod: external_exports.enum(ORDER_BILLING_PERIODS).nullable(),
|
|
20328
20341
|
periodLength: external_exports.number().int().nullable(),
|
|
20329
20342
|
trialPeriodDays: external_exports.number().int().nullable(),
|
|
20330
|
-
items: external_exports.array(createOrderItemResponseSchema)
|
|
20343
|
+
items: external_exports.array(createOrderItemResponseSchema),
|
|
20344
|
+
metadata: external_exports.record(external_exports.string(), external_exports.string()).default({})
|
|
20331
20345
|
});
|
|
20332
20346
|
|
|
20333
20347
|
// ../../packages/mcp-contracts/dist/product-name.js
|
|
@@ -20591,7 +20605,7 @@ function canonicalResourceUrl(value) {
|
|
|
20591
20605
|
}
|
|
20592
20606
|
|
|
20593
20607
|
// ../../packages/mcp-contracts/dist/mcp-session-expired.js
|
|
20594
|
-
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.";
|
|
20595
20609
|
|
|
20596
20610
|
// ../../packages/mcp-contracts/dist/list-products.js
|
|
20597
20611
|
var PLAN_TYPES3 = ["free-access", "one-time", "recurring"];
|
|
@@ -20698,13 +20712,28 @@ var mcpMerchantSummarySchema = external_exports.object({
|
|
|
20698
20712
|
name: external_exports.string()
|
|
20699
20713
|
});
|
|
20700
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
|
+
|
|
20701
20726
|
// ../../packages/mcp-contracts/dist/whoami.js
|
|
20702
20727
|
var mcpConnectionHostSchema = external_exports.enum(["sandbox", "live"]);
|
|
20703
20728
|
var mcpConnectionProofSchema = external_exports.object({
|
|
20704
20729
|
userId: external_exports.uuid(),
|
|
20705
20730
|
scopes: external_exports.array(external_exports.string()).nullable(),
|
|
20706
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.`),
|
|
20707
|
-
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.")
|
|
20708
20737
|
});
|
|
20709
20738
|
var whoamiOutputSchema = mcpConnectionProofSchema;
|
|
20710
20739
|
|
|
@@ -23018,6 +23047,12 @@ function parsePhoneNumber2(text, options2, metadata) {
|
|
|
23018
23047
|
}
|
|
23019
23048
|
}
|
|
23020
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
|
+
|
|
23021
23056
|
// ../../node_modules/.pnpm/libphonenumber-js@1.13.14/node_modules/libphonenumber-js/es6/isValidPhoneNumber.js
|
|
23022
23057
|
function _typeof7(o) {
|
|
23023
23058
|
"@babel/helpers - typeof";
|
|
@@ -23074,6 +23109,11 @@ function isValidPhoneNumber() {
|
|
|
23074
23109
|
return phoneNumber && phoneNumber.isValid() || false;
|
|
23075
23110
|
}
|
|
23076
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
|
+
|
|
23077
23117
|
// ../../node_modules/.pnpm/libphonenumber-js@1.13.14/node_modules/libphonenumber-js/min/exports/isValidPhoneNumber.js
|
|
23078
23118
|
function isValidPhoneNumber2() {
|
|
23079
23119
|
return withMetadataArgument(isValidPhoneNumber, arguments);
|
|
@@ -23082,11 +23122,21 @@ function isValidPhoneNumber2() {
|
|
|
23082
23122
|
// ../../packages/mcp-contracts/dist/merchant-phone.js
|
|
23083
23123
|
var E164_PATTERN = /^\+[1-9]\d{1,14}$/;
|
|
23084
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
|
+
}
|
|
23085
23133
|
function isDialablePhoneNumber(phone) {
|
|
23086
23134
|
return E164_PATTERN.test(phone) && isValidPhoneNumber2(phone) && !NANP_FICTIONAL_BLOCK.test(phone);
|
|
23087
23135
|
}
|
|
23088
23136
|
|
|
23089
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.`;
|
|
23090
23140
|
var createMerchantAccountInputSchema = external_exports.object({
|
|
23091
23141
|
name: external_exports.string().trim().min(1, {
|
|
23092
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."
|
|
@@ -23099,8 +23149,13 @@ var createMerchantAccountInputSchema = external_exports.object({
|
|
|
23099
23149
|
* it by SMS, so shape alone is not enough: an invented placeholder passes E.164 and the
|
|
23100
23150
|
* SMS silently goes nowhere.
|
|
23101
23151
|
*/
|
|
23102
|
-
phone: external_exports.string().trim().
|
|
23103
|
-
|
|
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
|
+
});
|
|
23104
23159
|
}).optional(),
|
|
23105
23160
|
/**
|
|
23106
23161
|
* Optional retry correlation id from a prior partial failure.
|
|
@@ -25438,6 +25493,7 @@ function formatPeriodCaps(caps) {
|
|
|
25438
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(", ")})`;
|
|
25439
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.";
|
|
25440
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.`;
|
|
25441
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.";
|
|
25442
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.`;
|
|
25443
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.';
|
|
@@ -25446,14 +25502,14 @@ var TRANSACTION_SCOPE_RULE = 'Requires the "transaction:read" scope, which exist
|
|
|
25446
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".`;
|
|
25447
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.";
|
|
25448
25504
|
var TOOL_DESCRIPTIONS = {
|
|
25449
|
-
[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.",
|
|
25450
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.',
|
|
25451
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.",
|
|
25452
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".',
|
|
25453
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.',
|
|
25454
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.",
|
|
25455
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.",
|
|
25456
|
-
[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.',
|
|
25457
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.',
|
|
25458
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.",
|
|
25459
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.",
|
|
@@ -25471,7 +25527,7 @@ var SOLO_TOOL_DESCRIPTION_OVERRIDES = {
|
|
|
25471
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.',
|
|
25472
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.",
|
|
25473
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.",
|
|
25474
|
-
[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,
|
|
25475
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.",
|
|
25476
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,
|
|
25477
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
|
|
@@ -25583,7 +25639,7 @@ var QUICKSTART_SKILL_SECTIONS = [
|
|
|
25583
25639
|
"`merchants` list means connected with no merchant yet. If the call is denied for",
|
|
25584
25640
|
"`merchant:read`, name that missing scope; do not say disconnected.",
|
|
25585
25641
|
"",
|
|
25586
|
-
"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`.",
|
|
25587
25643
|
"",
|
|
25588
25644
|
"### A2. Product",
|
|
25589
25645
|
"",
|
|
@@ -25597,15 +25653,9 @@ var QUICKSTART_SKILL_SECTIONS = [
|
|
|
25597
25653
|
"",
|
|
25598
25654
|
"### A3. Checkout URL",
|
|
25599
25655
|
"",
|
|
25600
|
-
"
|
|
25601
|
-
"",
|
|
25602
|
-
"`{{MANAGED_API_HOST_URL}}/payment/{planId}?isSandbox=true`",
|
|
25603
|
-
"",
|
|
25604
|
-
"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):",
|
|
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`.",
|
|
25605
25657
|
"",
|
|
25606
|
-
"`
|
|
25607
|
-
"",
|
|
25608
|
-
"`list_products` also returns `checkoutUrl` per plan. A sandbox checkout link never becomes this URL on its own; it keeps `?isSandbox=true` forever.",
|
|
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.",
|
|
25609
25659
|
"",
|
|
25610
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.",
|
|
25611
25661
|
"",
|
|
@@ -25630,24 +25680,26 @@ var QUICKSTART_SKILL_SECTIONS = [
|
|
|
25630
25680
|
"",
|
|
25631
25681
|
"## Tools",
|
|
25632
25682
|
"",
|
|
25633
|
-
"| Tool | Use
|
|
25634
|
-
"| -------------------------------- |
|
|
25635
|
-
"| `whoami` | `userId`, `scopes`, `host`, `endpoint`; any valid token
|
|
25636
|
-
"| `search_payments_ai_docs` | Search developer docs
|
|
25637
|
-
"| `get_my_merchant` | Owned merchants
|
|
25638
|
-
"| `create_merchant_account` | Dual-provision live + sandbox merchant
|
|
25639
|
-
"| `get_merchant_activation_status` | Activation status (see below)
|
|
25640
|
-
"| `go_live` | Promote to live; a repeat returns current status
|
|
25641
|
-
"| `create_product` | Product + plans (sandbox until go-live)
|
|
25642
|
-
"| `create_order` | Multi-item or caller-priced checkout
|
|
25643
|
-
"| `
|
|
25644
|
-
"| `
|
|
25645
|
-
"| `
|
|
25646
|
-
"| `
|
|
25647
|
-
"| `
|
|
25648
|
-
"| `
|
|
25649
|
-
"| `
|
|
25650
|
-
"| `
|
|
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 |",
|
|
25651
25703
|
"",
|
|
25652
25704
|
"### Activation status",
|
|
25653
25705
|
"",
|
|
@@ -25715,6 +25767,115 @@ async function resolveTransactionLookupBackend(input2) {
|
|
|
25715
25767
|
return { error: NOT_LIVE_YET_ERROR };
|
|
25716
25768
|
}
|
|
25717
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
|
+
|
|
25718
25879
|
// src/tools/tool-required-scopes.ts
|
|
25719
25880
|
var ANY_VALID_TOKEN = /* @__PURE__ */ Symbol("ANY_VALID_TOKEN");
|
|
25720
25881
|
var TOOL_REQUIRED_SCOPE = {
|
|
@@ -25787,6 +25948,36 @@ function filterToolsByScope(server, tokenPayload) {
|
|
|
25787
25948
|
});
|
|
25788
25949
|
}
|
|
25789
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
|
+
|
|
25790
25981
|
// src/tools/product/build-plan-checkout-url.ts
|
|
25791
25982
|
var CHECKOUT_SANDBOX_QUERY_PARAM = "isSandbox";
|
|
25792
25983
|
function buildPlanCheckoutUrl(params) {
|
|
@@ -26479,6 +26670,8 @@ var ORDER_DOC_SECTIONS = [
|
|
|
26479
26670
|
|
|
26480
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.
|
|
26481
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
|
+
|
|
26482
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.`
|
|
26483
26676
|
}
|
|
26484
26677
|
];
|
|
@@ -26629,7 +26822,7 @@ function runSearchPaymentsAiDocsHandler(query) {
|
|
|
26629
26822
|
// src/tools/connection/connection-proof.ts
|
|
26630
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.";
|
|
26631
26824
|
function buildConnectionProof(input2) {
|
|
26632
|
-
const { payload, host: host2, endpoint } = input2;
|
|
26825
|
+
const { payload, host: host2, endpoint, credentialExpiry } = input2;
|
|
26633
26826
|
if (!payload || !host2 || !endpoint) {
|
|
26634
26827
|
return void 0;
|
|
26635
26828
|
}
|
|
@@ -26637,7 +26830,11 @@ function buildConnectionProof(input2) {
|
|
|
26637
26830
|
userId: payload.userId,
|
|
26638
26831
|
scopes: payload.scopes,
|
|
26639
26832
|
host: host2,
|
|
26640
|
-
endpoint
|
|
26833
|
+
endpoint,
|
|
26834
|
+
merchantIds: payload.merchantIds,
|
|
26835
|
+
credentialExpiresAt: credentialExpiry.credentialExpiresAt,
|
|
26836
|
+
credentialExpiresInSeconds: credentialExpiry.credentialExpiresInSeconds,
|
|
26837
|
+
...credentialExpiry.warning ? { credentialExpiryWarning: credentialExpiry.warning } : {}
|
|
26641
26838
|
});
|
|
26642
26839
|
return parsed.success ? parsed.data : void 0;
|
|
26643
26840
|
}
|
|
@@ -27002,7 +27199,7 @@ function toolError(text) {
|
|
|
27002
27199
|
};
|
|
27003
27200
|
}
|
|
27004
27201
|
function resolveBackendClients(options2) {
|
|
27005
|
-
if (options2
|
|
27202
|
+
if (options2.backendClients) {
|
|
27006
27203
|
return {
|
|
27007
27204
|
sandbox: options2.backendClients.sandbox,
|
|
27008
27205
|
live: options2.backendClients.live
|
|
@@ -27025,12 +27222,18 @@ async function resolveProductCheckoutRoute(sandbox, live, merchantId) {
|
|
|
27025
27222
|
function registerTools(server, options2) {
|
|
27026
27223
|
const { sandbox: sandboxBackendClient, live: liveBackendClient } = resolveBackendClients(options2);
|
|
27027
27224
|
const dualBackendClients = sandboxBackendClient ? { sandbox: sandboxBackendClient, live: liveBackendClient } : void 0;
|
|
27028
|
-
const deploymentRouter2 = options2
|
|
27029
|
-
const serverDeployment = options2
|
|
27030
|
-
const mcpTokenPayload = options2
|
|
27031
|
-
const checkoutBaseUrl2 = options2
|
|
27032
|
-
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;
|
|
27033
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
|
+
});
|
|
27034
27237
|
const denyForTool = (name) => scopeDenial(TOOL_REQUIRED_SCOPE[name], mcpTokenPayload, dashboardUrl);
|
|
27035
27238
|
async function resolveCallerMerchantIds(router, payload) {
|
|
27036
27239
|
const allowlist = await router.resolveMerchantAllowlist(payload);
|
|
@@ -27093,7 +27296,8 @@ function registerTools(server, options2) {
|
|
|
27093
27296
|
return runWhoamiHandler({
|
|
27094
27297
|
payload: mcpTokenPayload,
|
|
27095
27298
|
host: serverDeployment,
|
|
27096
|
-
endpoint: options2
|
|
27299
|
+
endpoint: options2.mcpPublicUrl,
|
|
27300
|
+
credentialExpiry
|
|
27097
27301
|
});
|
|
27098
27302
|
}
|
|
27099
27303
|
);
|
|
@@ -27703,6 +27907,7 @@ function registerTools(server, options2) {
|
|
|
27703
27907
|
}
|
|
27704
27908
|
);
|
|
27705
27909
|
filterToolsByScope(server, mcpTokenPayload);
|
|
27910
|
+
attachCredentialExpiryWarning(server, credentialExpiry.warning);
|
|
27706
27911
|
}
|
|
27707
27912
|
|
|
27708
27913
|
// src/http/connect-diagnostics/connect-diagnostics-intake.ts
|
|
@@ -28042,17 +28247,27 @@ function parseMcpTokenPayload(parsed) {
|
|
|
28042
28247
|
if (typeof parsed !== "object" || parsed === null) {
|
|
28043
28248
|
return void 0;
|
|
28044
28249
|
}
|
|
28045
|
-
const { userId, cognitoSub, merchantIds, scopes } = parsed;
|
|
28250
|
+
const { userId, cognitoSub, merchantIds, scopes, expiresAt, oauthIssued } = parsed;
|
|
28046
28251
|
if (typeof userId !== "string" || typeof cognitoSub !== "string" || !isStringArray(merchantIds)) {
|
|
28047
28252
|
return void 0;
|
|
28048
28253
|
}
|
|
28254
|
+
const expiry = parseExpiresAt(expiresAt);
|
|
28049
28255
|
return {
|
|
28050
28256
|
userId,
|
|
28051
28257
|
cognitoSub,
|
|
28052
28258
|
merchantIds,
|
|
28053
|
-
scopes: Array.isArray(scopes) ? scopes : null
|
|
28259
|
+
scopes: Array.isArray(scopes) ? scopes : null,
|
|
28260
|
+
oauthIssued: oauthIssued === true,
|
|
28261
|
+
...expiry === void 0 ? {} : { expiresAt: expiry }
|
|
28054
28262
|
};
|
|
28055
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
|
+
}
|
|
28056
28271
|
|
|
28057
28272
|
// src/auth/mcp-token-audience.ts
|
|
28058
28273
|
function isTokenAudienceAccepted(audience, mcpPublicUrl2) {
|
|
@@ -28066,63 +28281,17 @@ function isTokenAudienceAccepted(audience, mcpPublicUrl2) {
|
|
|
28066
28281
|
return canonicalAudience !== null && canonicalAudience === canonicalResourceUrl(mcpPublicUrl2);
|
|
28067
28282
|
}
|
|
28068
28283
|
|
|
28069
|
-
// src/auth/mcp-auth-rejection.ts
|
|
28070
|
-
var MCP_AUTH_ERROR_DOC_SLUG = {
|
|
28071
|
-
missing_token: "missing-token",
|
|
28072
|
-
invalid_token: "invalid-token",
|
|
28073
|
-
token_expired: "token-expired"
|
|
28074
|
-
};
|
|
28075
|
-
var DEPLOYMENT_LABEL = {
|
|
28076
|
-
sandbox: "Sandbox",
|
|
28077
|
-
live: "Live"
|
|
28078
|
-
};
|
|
28079
|
-
var MCP_TOKENS_DASHBOARD_PATH = "Developer Tools \u2192 MCP Tokens";
|
|
28080
|
-
var MESSAGE_BY_CODE = {
|
|
28081
|
-
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.`,
|
|
28082
|
-
invalid_token: (context) => {
|
|
28083
|
-
const here = DEPLOYMENT_LABEL[context.serverDeployment];
|
|
28084
|
-
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.`;
|
|
28085
|
-
},
|
|
28086
|
-
token_expired: (context) => context.expiredMessage ?? MCP_SESSION_EXPIRED_MESSAGE,
|
|
28087
|
-
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."
|
|
28088
|
-
};
|
|
28089
|
-
function resolveDocsBase(docsBaseUrl2) {
|
|
28090
|
-
return (docsBaseUrl2 ?? DEFAULT_DOCS_BASE_URL).replace(/\/+$/, "");
|
|
28091
|
-
}
|
|
28092
|
-
function buildMcpAuthErrorDocUrl(code, docsBaseUrl2) {
|
|
28093
|
-
const base = resolveDocsBase(docsBaseUrl2);
|
|
28094
|
-
const slug = MCP_AUTH_ERROR_DOC_SLUG[code];
|
|
28095
|
-
return slug === void 0 ? `${base}/errors` : `${base}/errors/${slug}`;
|
|
28096
|
-
}
|
|
28097
|
-
function buildMcpConnectPageUrl(docsBaseUrl2) {
|
|
28098
|
-
return `${resolveDocsBase(docsBaseUrl2)}/connect`;
|
|
28099
|
-
}
|
|
28100
|
-
function buildMcpConnectDiagnosticsUrl(mcpPath2, mcpPublicUrl2) {
|
|
28101
|
-
const endpoint = mcpPublicUrl2 === void 0 ? mcpPath2 : mcpPublicUrl2.replace(/\/+$/, "");
|
|
28102
|
-
return `${endpoint}${CONNECT_DIAGNOSTICS_PATH_SUFFIX}`;
|
|
28103
|
-
}
|
|
28104
|
-
function buildMcpAuthRejectionBody(code, context, links) {
|
|
28105
|
-
return {
|
|
28106
|
-
statusCode: 401,
|
|
28107
|
-
error: "Unauthorized",
|
|
28108
|
-
code,
|
|
28109
|
-
message: MESSAGE_BY_CODE[code](context),
|
|
28110
|
-
docUrl: buildMcpAuthErrorDocUrl(code, links.docsBaseUrl),
|
|
28111
|
-
connectDiagnosticsUrl: links.connectDiagnosticsUrl,
|
|
28112
|
-
connectPageUrl: buildMcpConnectPageUrl(links.docsBaseUrl)
|
|
28113
|
-
};
|
|
28114
|
-
}
|
|
28115
|
-
function formatMcpAuthRejectionLog(code, mode) {
|
|
28116
|
-
return JSON.stringify({ event: "mcp.auth_rejected", code, mode });
|
|
28117
|
-
}
|
|
28118
|
-
|
|
28119
28284
|
// src/http/app.ts
|
|
28120
28285
|
var LOCALHOST_BINDS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1"]);
|
|
28121
28286
|
var LOCALHOST_ALLOWED_HOSTS = ["127.0.0.1", "localhost", "::1", "[::1]"];
|
|
28122
28287
|
var BEARER_PREFIX = "Bearer ";
|
|
28123
28288
|
var MCP_TOKEN_FORMAT = /^pai_mcp_[0-9a-f]{64}$/;
|
|
28124
|
-
function reject(code,
|
|
28125
|
-
return {
|
|
28289
|
+
function reject(code, options2 = {}) {
|
|
28290
|
+
return {
|
|
28291
|
+
ok: false,
|
|
28292
|
+
rejection: { code, reason: options2.reason ?? code },
|
|
28293
|
+
expiredMessage: options2.expiredMessage
|
|
28294
|
+
};
|
|
28126
28295
|
}
|
|
28127
28296
|
function decideFromOutcome(token, outcome, mcpPublicUrl2) {
|
|
28128
28297
|
if (outcome.ok) {
|
|
@@ -28130,7 +28299,7 @@ function decideFromOutcome(token, outcome, mcpPublicUrl2) {
|
|
|
28130
28299
|
}
|
|
28131
28300
|
switch (outcome.reason) {
|
|
28132
28301
|
case "expired":
|
|
28133
|
-
return reject("token_expired", outcome.expiredMessage);
|
|
28302
|
+
return reject("token_expired", { expiredMessage: outcome.expiredMessage });
|
|
28134
28303
|
case "unavailable":
|
|
28135
28304
|
return reject("token_validation_unavailable");
|
|
28136
28305
|
case "not_found":
|
|
@@ -28152,7 +28321,7 @@ async function authenticateMcpRequest(authHeader, validationClient, mcpPublicUrl
|
|
|
28152
28321
|
}
|
|
28153
28322
|
const token = authHeader.slice(BEARER_PREFIX.length);
|
|
28154
28323
|
if (!MCP_TOKEN_FORMAT.test(token)) {
|
|
28155
|
-
return reject("invalid_token");
|
|
28324
|
+
return reject("invalid_token", { reason: "malformed_credential" });
|
|
28156
28325
|
}
|
|
28157
28326
|
const outcome = await validateMcpToken(token, validationClient);
|
|
28158
28327
|
return decideFromOutcome(token, outcome, mcpPublicUrl2);
|
|
@@ -28182,7 +28351,8 @@ function createExpressApp(options2) {
|
|
|
28182
28351
|
websiteUrl,
|
|
28183
28352
|
checkoutBaseUrl: checkoutBaseUrl2,
|
|
28184
28353
|
allowedMcpOriginHostnames = DEFAULT_ALLOWED_MCP_ORIGIN_HOSTNAMES,
|
|
28185
|
-
deploymentRouter: deploymentRouterOption
|
|
28354
|
+
deploymentRouter: deploymentRouterOption,
|
|
28355
|
+
now = () => /* @__PURE__ */ new Date()
|
|
28186
28356
|
} = options2;
|
|
28187
28357
|
const deploymentRouter2 = deploymentRouterOption ?? (backendClients2 && hasLiveBackend(backendClients2) ? createDualRouter(backendClients2) : void 0);
|
|
28188
28358
|
const tokenValidationClient = deploymentRouter2?.tokenValidationClient ?? backendClients2?.live ?? backendClients2?.sandbox;
|
|
@@ -28259,17 +28429,17 @@ function createExpressApp(options2) {
|
|
|
28259
28429
|
const challengeScope = challengeOAuthScopes(deploymentRouter2).join(" ");
|
|
28260
28430
|
const mcpOriginMiddleware = createMcpOriginMiddleware(allowedMcpOriginHostnames);
|
|
28261
28431
|
const connectDiagnosticsUrl = buildMcpConnectDiagnosticsUrl(mcpPath2, mcpPublicUrl2);
|
|
28262
|
-
const sendUnauthorized = (res,
|
|
28432
|
+
const sendUnauthorized = (res, rejection, expiredMessage) => {
|
|
28263
28433
|
if (resourceMetadataUrl) {
|
|
28264
28434
|
res.set(
|
|
28265
28435
|
"WWW-Authenticate",
|
|
28266
28436
|
`Bearer realm="PAI Managed MCP", resource_metadata="${resourceMetadataUrl}", scope="${challengeScope}"`
|
|
28267
28437
|
);
|
|
28268
28438
|
}
|
|
28269
|
-
console.error(formatMcpAuthRejectionLog(
|
|
28439
|
+
console.error(formatMcpAuthRejectionLog(rejection, routerMode));
|
|
28270
28440
|
res.status(401).json(
|
|
28271
28441
|
buildMcpAuthRejectionBody(
|
|
28272
|
-
code,
|
|
28442
|
+
rejection.code,
|
|
28273
28443
|
{ serverDeployment, expiredMessage },
|
|
28274
28444
|
{ docsBaseUrl: docsBaseUrl2, connectDiagnosticsUrl }
|
|
28275
28445
|
)
|
|
@@ -28289,7 +28459,7 @@ function createExpressApp(options2) {
|
|
|
28289
28459
|
return;
|
|
28290
28460
|
}
|
|
28291
28461
|
if (!decision.ok) {
|
|
28292
|
-
sendUnauthorized(res, decision.
|
|
28462
|
+
sendUnauthorized(res, decision.rejection, decision.expiredMessage);
|
|
28293
28463
|
return;
|
|
28294
28464
|
}
|
|
28295
28465
|
req.mcpTokenPayload = decision.payload;
|
|
@@ -28327,7 +28497,9 @@ function createExpressApp(options2) {
|
|
|
28327
28497
|
checkoutBaseUrl: checkoutBaseUrl2,
|
|
28328
28498
|
dashboardUrl: websiteUrl,
|
|
28329
28499
|
mcpPublicUrl: mcpPublicUrl2,
|
|
28330
|
-
serverDeployment
|
|
28500
|
+
serverDeployment,
|
|
28501
|
+
docsBaseUrl: docsBaseUrl2,
|
|
28502
|
+
now
|
|
28331
28503
|
});
|
|
28332
28504
|
registerPrompts(server, {
|
|
28333
28505
|
servesGoLive: servesGoLive(deploymentRouter2),
|