@solvapay/mcp-core 0.4.6 → 0.4.7
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/index.cjs +50 -34
- package/dist/index.d.cts +19 -14
- package/dist/index.d.ts +19 -14
- package/dist/index.js +50 -34
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -238,6 +238,9 @@ function limitsAsSignals(limits) {
|
|
|
238
238
|
if (!limits) return null;
|
|
239
239
|
return limits;
|
|
240
240
|
}
|
|
241
|
+
function creditsUnused(customer) {
|
|
242
|
+
return !(0, import_server.creditSignals)(limitsAsSignals(customer?.limits)).isCreditBased;
|
|
243
|
+
}
|
|
241
244
|
function preferLimitsPlan(purchase, limits) {
|
|
242
245
|
const limitsRef = limits?.planRef;
|
|
243
246
|
if (!limitsRef) return false;
|
|
@@ -252,6 +255,12 @@ function balanceSummary(customer) {
|
|
|
252
255
|
if (!row) return null;
|
|
253
256
|
return row.replace(/^Balance:\s*/, "");
|
|
254
257
|
}
|
|
258
|
+
function identityRow(customer) {
|
|
259
|
+
const ref = customer?.ref?.trim();
|
|
260
|
+
if (!ref) return null;
|
|
261
|
+
const email = customer?.email?.trim();
|
|
262
|
+
return email ? `Signed in as: ${email} \xB7 ${ref}` : `Signed in as: ${ref}`;
|
|
263
|
+
}
|
|
255
264
|
function balanceRow(customer) {
|
|
256
265
|
if (!customer?.balance) return null;
|
|
257
266
|
const credits = customer.balance.credits ?? 0;
|
|
@@ -593,8 +602,7 @@ function narrateAccountBody(input) {
|
|
|
593
602
|
} else {
|
|
594
603
|
position = "After your first call";
|
|
595
604
|
}
|
|
596
|
-
|
|
597
|
-
return `${product} is on ${planName}${priceBit}. ${position}. ` + (creditsUnused ? `Credits are not used on this plan. Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.` : `Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.`);
|
|
605
|
+
return `${product} is on ${planName}${priceBit}. ${position}. ` + (creditsUnused(customer) ? `Credits are not used on this plan. Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.` : `Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.`);
|
|
598
606
|
}
|
|
599
607
|
if (state === "E") {
|
|
600
608
|
const left2 = remainingOfTotal(usage);
|
|
@@ -617,7 +625,7 @@ function narrateAccountBody(input) {
|
|
|
617
625
|
return otherShape !== "free" && otherShape !== "trial";
|
|
618
626
|
});
|
|
619
627
|
const carryOn = others.length > 0 ? ` ${joinOr(others.map((item) => carryOnFragment(item, customer)))}.` : "";
|
|
620
|
-
const antiTrap =
|
|
628
|
+
const antiTrap = creditsUnused(customer) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
621
629
|
const priceBit = planPriceBit(plan);
|
|
622
630
|
return `${product} is on ${planName}${priceBit}. ${capBit}.${consequence}.${antiTrap}${carryOn} Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch plan.`;
|
|
623
631
|
}
|
|
@@ -704,6 +712,8 @@ function narrateManageAccount(data, options) {
|
|
|
704
712
|
now
|
|
705
713
|
})
|
|
706
714
|
);
|
|
715
|
+
const identity = identityRow(customer);
|
|
716
|
+
if (identity) lines.push(identity);
|
|
707
717
|
if (state === "A" || state === "H") {
|
|
708
718
|
const bal = balanceRow(customer);
|
|
709
719
|
if (bal) lines.push(bal);
|
|
@@ -904,6 +914,15 @@ function enrichPurchase(purchase) {
|
|
|
904
914
|
...enrichedPlanSnapshot !== void 0 ? { planSnapshot: enrichedPlanSnapshot } : {}
|
|
905
915
|
};
|
|
906
916
|
}
|
|
917
|
+
function signedInCustomerRefOnly(getCustomerRef) {
|
|
918
|
+
return (extra) => {
|
|
919
|
+
const ref = getCustomerRef(extra);
|
|
920
|
+
if (typeof ref !== "string") return null;
|
|
921
|
+
const trimmed = ref.trim();
|
|
922
|
+
if (!trimmed || trimmed === "anonymous") return null;
|
|
923
|
+
return trimmed;
|
|
924
|
+
};
|
|
925
|
+
}
|
|
907
926
|
function defaultGetCustomerRef(extra) {
|
|
908
927
|
const candidates = [
|
|
909
928
|
extra?.http?.authInfo?.extra?.customer_ref,
|
|
@@ -1029,13 +1048,9 @@ var VIEW_PRIORITY = ["checkout", "topup", "account"];
|
|
|
1029
1048
|
function hasActivePlan(customer, productRef) {
|
|
1030
1049
|
return selectActivePlanPurchase(customer?.purchase?.purchases, productRef) !== null;
|
|
1031
1050
|
}
|
|
1032
|
-
function isOutOfCredits(customer) {
|
|
1033
|
-
const credits = customer?.balance?.credits;
|
|
1034
|
-
return credits === 0;
|
|
1035
|
-
}
|
|
1036
1051
|
function deriveDefaultView(data, enabledViews = new Set(SOLVAPAY_MCP_ADVERTISED_VIEW_KINDS)) {
|
|
1037
1052
|
const customer = data.customer;
|
|
1038
|
-
const preferred = !hasActivePlan(customer, data.productRef) ? "checkout" :
|
|
1053
|
+
const preferred = !hasActivePlan(customer, data.productRef) ? "checkout" : customer?.nextAction === "topup" ? "topup" : "account";
|
|
1039
1054
|
if (enabledViews.has(preferred)) return preferred;
|
|
1040
1055
|
for (const view of VIEW_PRIORITY) {
|
|
1041
1056
|
if (enabledViews.has(view)) return view;
|
|
@@ -1251,12 +1266,10 @@ var createBootstrapProductError = (productResult) => {
|
|
|
1251
1266
|
return err;
|
|
1252
1267
|
};
|
|
1253
1268
|
function createBuildBootstrapPayload(options) {
|
|
1254
|
-
const {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
getCustomerRef = defaultGetCustomerRef
|
|
1259
|
-
} = options;
|
|
1269
|
+
const { solvaPay, productRef, publicBaseUrl } = options;
|
|
1270
|
+
const getCustomerRef = signedInCustomerRefOnly(
|
|
1271
|
+
options.getCustomerRef ?? defaultGetCustomerRef
|
|
1272
|
+
);
|
|
1260
1273
|
const fetchPublishableKey = async () => {
|
|
1261
1274
|
try {
|
|
1262
1275
|
const platform = await solvaPay.apiClient.getPlatformConfig?.();
|
|
@@ -1311,18 +1324,16 @@ function createBuildBootstrapPayload(options) {
|
|
|
1311
1324
|
}
|
|
1312
1325
|
}
|
|
1313
1326
|
const checkoutPurpose = view === "topup" || view !== "checkout" && view !== "auto-recharge" && limits?.paywallReason === "topup_required" ? "credit_topup" : void 0;
|
|
1314
|
-
const checkoutResult = await wrapError(
|
|
1327
|
+
const checkoutResult = customerRef ? await wrapError(
|
|
1315
1328
|
createMcpCheckoutSession(
|
|
1316
|
-
|
|
1317
|
-
getCustomerRef: () => customerRef ?? "anonymous"
|
|
1318
|
-
}),
|
|
1329
|
+
buildRequest(extra),
|
|
1319
1330
|
{
|
|
1320
1331
|
productRef,
|
|
1321
1332
|
...checkoutPurpose ? { purpose: checkoutPurpose } : {}
|
|
1322
1333
|
},
|
|
1323
1334
|
{ solvaPay }
|
|
1324
1335
|
)
|
|
1325
|
-
);
|
|
1336
|
+
) : await unauthenticated();
|
|
1326
1337
|
if ((0, import_server4.isErrorResult)(merchantResult)) {
|
|
1327
1338
|
throw createBootstrapMerchantError(merchantResult);
|
|
1328
1339
|
}
|
|
@@ -1345,19 +1356,16 @@ function createBuildBootstrapPayload(options) {
|
|
|
1345
1356
|
} : null;
|
|
1346
1357
|
const activePurchase2 = selectActivePlanPurchase(enrichedPurchase?.purchases, productRef);
|
|
1347
1358
|
const usage = customerRef ? (0, import_server4.deriveUsageSnapshot)({
|
|
1348
|
-
// Consumption comes from `limits.used` (or `limit - remaining`)
|
|
1349
|
-
// inside `deriveUsageSnapshot`. `purchase.usage.used` is only
|
|
1350
|
-
// ever reset to zero by the backend and must not be the source.
|
|
1351
|
-
used: 0,
|
|
1352
1359
|
periodStart: activePurchase2?.usage?.periodStart,
|
|
1353
1360
|
periodEnd: activePurchase2?.usage?.periodEnd,
|
|
1354
1361
|
purchaseRef: activePurchase2?.reference,
|
|
1355
1362
|
limits
|
|
1356
1363
|
}) : null;
|
|
1357
1364
|
const signals = limits ? (0, import_server4.creditSignals)(limits) : null;
|
|
1358
|
-
const state = limits ? (0, import_server4.classifyPaywallState)(limits) : null;
|
|
1359
1365
|
const customer = customerRef ? {
|
|
1360
1366
|
ref: customerRef,
|
|
1367
|
+
email: purchase?.email?.trim() ? purchase.email.trim() : null,
|
|
1368
|
+
name: purchase?.name?.trim() ? purchase.name.trim() : null,
|
|
1361
1369
|
purchase: enrichedPurchase,
|
|
1362
1370
|
paymentMethod: okOrNull(paymentMethodResult),
|
|
1363
1371
|
balance: okOrNull(balanceResult),
|
|
@@ -1367,8 +1375,9 @@ function createBuildBootstrapPayload(options) {
|
|
|
1367
1375
|
...signals?.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
1368
1376
|
...signals?.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
1369
1377
|
...signals?.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
1378
|
+
...signals ? { isCreditBased: signals.isCreditBased } : {},
|
|
1370
1379
|
...limits?.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
1371
|
-
...
|
|
1380
|
+
...limits?.withinLimits === false ? { nextAction: (0, import_server4.nextActionFor)((0, import_server4.classifyPaywallState)(limits)) } : {}
|
|
1372
1381
|
} : null;
|
|
1373
1382
|
const checkout = okOrNull(checkoutResult);
|
|
1374
1383
|
const portal = okOrNull(portalResult);
|
|
@@ -1479,11 +1488,13 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1479
1488
|
views = DEFAULT_VIEWS,
|
|
1480
1489
|
csp,
|
|
1481
1490
|
apiBaseUrl,
|
|
1482
|
-
getCustomerRef = defaultGetCustomerRef,
|
|
1483
1491
|
onToolCall,
|
|
1484
1492
|
onToolResult,
|
|
1485
1493
|
branding
|
|
1486
1494
|
} = options;
|
|
1495
|
+
const getCustomerRef = signedInCustomerRefOnly(
|
|
1496
|
+
options.getCustomerRef ?? defaultGetCustomerRef
|
|
1497
|
+
);
|
|
1487
1498
|
const toolIcons = deriveIcons(branding);
|
|
1488
1499
|
if (!/^https?:\/\//i.test(publicBaseUrl)) {
|
|
1489
1500
|
throw new Error(
|
|
@@ -1561,6 +1572,11 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1561
1572
|
publicBaseUrl,
|
|
1562
1573
|
getCustomerRef
|
|
1563
1574
|
});
|
|
1575
|
+
const buildDefaultBootstrapPayload = async (extra) => {
|
|
1576
|
+
const data = await buildBootstrapPayload("account", extra);
|
|
1577
|
+
data.view = deriveDefaultView(data, enabledViews);
|
|
1578
|
+
return data;
|
|
1579
|
+
};
|
|
1564
1580
|
const advertisedViewList = SOLVAPAY_MCP_ADVERTISED_VIEW_KINDS.filter(
|
|
1565
1581
|
(view) => enabledViews.has(view)
|
|
1566
1582
|
);
|
|
@@ -1590,10 +1606,9 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1590
1606
|
});
|
|
1591
1607
|
}
|
|
1592
1608
|
const mode = parseMode(args.mode);
|
|
1593
|
-
const data = await buildBootstrapPayload(requested
|
|
1594
|
-
|
|
1595
|
-
data.view
|
|
1596
|
-
return narratedToolResult(view, data, mode, {
|
|
1609
|
+
const data = requested === void 0 ? await buildDefaultBootstrapPayload(extra) : await buildBootstrapPayload(requested, extra);
|
|
1610
|
+
if (requested !== void 0) data.view = requested;
|
|
1611
|
+
return narratedToolResult(data.view, data, mode, {
|
|
1597
1612
|
...toolMeta,
|
|
1598
1613
|
"openai/widgetSessionId": crypto.randomUUID()
|
|
1599
1614
|
});
|
|
@@ -1950,10 +1965,11 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1950
1965
|
title: "SolvaPay bootstrap",
|
|
1951
1966
|
description: "Current merchant/product/plans/customer snapshot for the embedded UI. Widgets read this idempotently when the host scrubs structuredContent from tool results.",
|
|
1952
1967
|
mimeType: SOLVAPAY_BOOTSTRAP_MIME_TYPE,
|
|
1953
|
-
// View is
|
|
1954
|
-
//
|
|
1955
|
-
//
|
|
1956
|
-
|
|
1968
|
+
// View is the server's default for this customer (checkout with no
|
|
1969
|
+
// active plan, topup on a top-up gate, account otherwise) — the same
|
|
1970
|
+
// choice the viewer makes when `view` is omitted. The widget trusts
|
|
1971
|
+
// this field; it does not resolve the surface from host context.
|
|
1972
|
+
readPayload: (extra) => buildDefaultBootstrapPayload(extra)
|
|
1957
1973
|
};
|
|
1958
1974
|
return { tools, resource, prompts, docsResources, bootstrapResource, buildBootstrapPayload };
|
|
1959
1975
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -82,6 +82,14 @@ type BootstrapPlan = components['schemas']['Plan'];
|
|
|
82
82
|
*/
|
|
83
83
|
interface BootstrapCustomer {
|
|
84
84
|
ref: string;
|
|
85
|
+
/**
|
|
86
|
+
* Signed-in identity from the customer record. Projected off the
|
|
87
|
+
* purchase-check result, which carries them only because that helper
|
|
88
|
+
* looks the customer up. Null when that sub-read returned none.
|
|
89
|
+
* The whole `customer` is null for an anonymous caller.
|
|
90
|
+
*/
|
|
91
|
+
email: string | null;
|
|
92
|
+
name: string | null;
|
|
85
93
|
purchase: PurchaseCheckResult | null;
|
|
86
94
|
paymentMethod: PaymentMethodInfo | null;
|
|
87
95
|
balance: CustomerBalanceResult | null;
|
|
@@ -101,9 +109,14 @@ interface BootstrapCustomer {
|
|
|
101
109
|
creditsPerCall?: number;
|
|
102
110
|
/** `max(0, creditsPerCall - balance)` when both are known. */
|
|
103
111
|
shortfallCredits?: number;
|
|
112
|
+
/**
|
|
113
|
+
* True when the limits response carries credit-balance fields.
|
|
114
|
+
* False means the plan does not spend credits.
|
|
115
|
+
*/
|
|
116
|
+
isCreditBased?: boolean;
|
|
104
117
|
/** Per-provider auto-recharge snapshot from the limits response. */
|
|
105
118
|
autoRecharge?: LimitAutoRechargeDto;
|
|
106
|
-
/**
|
|
119
|
+
/** Present only when the latest `checkLimits` blocked the call. */
|
|
107
120
|
nextAction?: PaywallNextAction;
|
|
108
121
|
}
|
|
109
122
|
/**
|
|
@@ -725,16 +738,6 @@ declare const ToolErrorEnvelopeSchema: z.ZodObject<{
|
|
|
725
738
|
* programmatic consumers (e.g. the React transport).
|
|
726
739
|
*/
|
|
727
740
|
declare function enrichPurchase(purchase: Record<string, unknown>): Record<string, unknown>;
|
|
728
|
-
/**
|
|
729
|
-
* Default extractor for `customer_ref` out of the MCP OAuth bridge
|
|
730
|
-
* (what `auth-bridge.ts` populates), trimmed. Returns `null` when no
|
|
731
|
-
* ref is present.
|
|
732
|
-
*
|
|
733
|
-
* Checks `extra.http.authInfo` first — the official SDK v2 location —
|
|
734
|
-
* then falls back to the flat v1 `extra.authInfo` still emitted by some
|
|
735
|
-
* third-party adapters. Reading only the flat location silently
|
|
736
|
-
* de-authenticated every tool call under SDK v2.
|
|
737
|
-
*/
|
|
738
741
|
declare function defaultGetCustomerRef(extra?: McpToolExtra): string | null;
|
|
739
742
|
interface BuildSolvaPayRequestOptions {
|
|
740
743
|
method?: string;
|
|
@@ -911,7 +914,7 @@ interface PurchaseShape {
|
|
|
911
914
|
};
|
|
912
915
|
}
|
|
913
916
|
interface UsageShape {
|
|
914
|
-
used?: number;
|
|
917
|
+
used?: number | null;
|
|
915
918
|
total?: number | null;
|
|
916
919
|
remaining?: number | null;
|
|
917
920
|
periodEnd?: string;
|
|
@@ -934,7 +937,9 @@ interface LimitsShape {
|
|
|
934
937
|
};
|
|
935
938
|
}
|
|
936
939
|
interface CustomerShape {
|
|
937
|
-
ref?: string;
|
|
940
|
+
ref?: string | null;
|
|
941
|
+
email?: string | null;
|
|
942
|
+
name?: string | null;
|
|
938
943
|
balance?: {
|
|
939
944
|
credits?: number | null;
|
|
940
945
|
displayCurrency?: string;
|
|
@@ -984,7 +989,7 @@ declare function uiPlaceholder(view: SolvaPayMcpViewKind, data: BootstrapPayload
|
|
|
984
989
|
* The three surfaces share one payload; omitting `view` must still land
|
|
985
990
|
* somewhere useful instead of failing. Rule:
|
|
986
991
|
* - no active plan → checkout (plans)
|
|
987
|
-
* - active plan and
|
|
992
|
+
* - active plan and blocked by a top-up gate → topup
|
|
988
993
|
* - otherwise → account
|
|
989
994
|
*
|
|
990
995
|
* When the preferred view is disabled on this server, fall through the
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,14 @@ type BootstrapPlan = components['schemas']['Plan'];
|
|
|
82
82
|
*/
|
|
83
83
|
interface BootstrapCustomer {
|
|
84
84
|
ref: string;
|
|
85
|
+
/**
|
|
86
|
+
* Signed-in identity from the customer record. Projected off the
|
|
87
|
+
* purchase-check result, which carries them only because that helper
|
|
88
|
+
* looks the customer up. Null when that sub-read returned none.
|
|
89
|
+
* The whole `customer` is null for an anonymous caller.
|
|
90
|
+
*/
|
|
91
|
+
email: string | null;
|
|
92
|
+
name: string | null;
|
|
85
93
|
purchase: PurchaseCheckResult | null;
|
|
86
94
|
paymentMethod: PaymentMethodInfo | null;
|
|
87
95
|
balance: CustomerBalanceResult | null;
|
|
@@ -101,9 +109,14 @@ interface BootstrapCustomer {
|
|
|
101
109
|
creditsPerCall?: number;
|
|
102
110
|
/** `max(0, creditsPerCall - balance)` when both are known. */
|
|
103
111
|
shortfallCredits?: number;
|
|
112
|
+
/**
|
|
113
|
+
* True when the limits response carries credit-balance fields.
|
|
114
|
+
* False means the plan does not spend credits.
|
|
115
|
+
*/
|
|
116
|
+
isCreditBased?: boolean;
|
|
104
117
|
/** Per-provider auto-recharge snapshot from the limits response. */
|
|
105
118
|
autoRecharge?: LimitAutoRechargeDto;
|
|
106
|
-
/**
|
|
119
|
+
/** Present only when the latest `checkLimits` blocked the call. */
|
|
107
120
|
nextAction?: PaywallNextAction;
|
|
108
121
|
}
|
|
109
122
|
/**
|
|
@@ -725,16 +738,6 @@ declare const ToolErrorEnvelopeSchema: z.ZodObject<{
|
|
|
725
738
|
* programmatic consumers (e.g. the React transport).
|
|
726
739
|
*/
|
|
727
740
|
declare function enrichPurchase(purchase: Record<string, unknown>): Record<string, unknown>;
|
|
728
|
-
/**
|
|
729
|
-
* Default extractor for `customer_ref` out of the MCP OAuth bridge
|
|
730
|
-
* (what `auth-bridge.ts` populates), trimmed. Returns `null` when no
|
|
731
|
-
* ref is present.
|
|
732
|
-
*
|
|
733
|
-
* Checks `extra.http.authInfo` first — the official SDK v2 location —
|
|
734
|
-
* then falls back to the flat v1 `extra.authInfo` still emitted by some
|
|
735
|
-
* third-party adapters. Reading only the flat location silently
|
|
736
|
-
* de-authenticated every tool call under SDK v2.
|
|
737
|
-
*/
|
|
738
741
|
declare function defaultGetCustomerRef(extra?: McpToolExtra): string | null;
|
|
739
742
|
interface BuildSolvaPayRequestOptions {
|
|
740
743
|
method?: string;
|
|
@@ -911,7 +914,7 @@ interface PurchaseShape {
|
|
|
911
914
|
};
|
|
912
915
|
}
|
|
913
916
|
interface UsageShape {
|
|
914
|
-
used?: number;
|
|
917
|
+
used?: number | null;
|
|
915
918
|
total?: number | null;
|
|
916
919
|
remaining?: number | null;
|
|
917
920
|
periodEnd?: string;
|
|
@@ -934,7 +937,9 @@ interface LimitsShape {
|
|
|
934
937
|
};
|
|
935
938
|
}
|
|
936
939
|
interface CustomerShape {
|
|
937
|
-
ref?: string;
|
|
940
|
+
ref?: string | null;
|
|
941
|
+
email?: string | null;
|
|
942
|
+
name?: string | null;
|
|
938
943
|
balance?: {
|
|
939
944
|
credits?: number | null;
|
|
940
945
|
displayCurrency?: string;
|
|
@@ -984,7 +989,7 @@ declare function uiPlaceholder(view: SolvaPayMcpViewKind, data: BootstrapPayload
|
|
|
984
989
|
* The three surfaces share one payload; omitting `view` must still land
|
|
985
990
|
* somewhere useful instead of failing. Rule:
|
|
986
991
|
* - no active plan → checkout (plans)
|
|
987
|
-
* - active plan and
|
|
992
|
+
* - active plan and blocked by a top-up gate → topup
|
|
988
993
|
* - otherwise → account
|
|
989
994
|
*
|
|
990
995
|
* When the preferred view is disabled on this server, fall through the
|
package/dist/index.js
CHANGED
|
@@ -147,6 +147,9 @@ function limitsAsSignals(limits) {
|
|
|
147
147
|
if (!limits) return null;
|
|
148
148
|
return limits;
|
|
149
149
|
}
|
|
150
|
+
function creditsUnused(customer) {
|
|
151
|
+
return !creditSignals(limitsAsSignals(customer?.limits)).isCreditBased;
|
|
152
|
+
}
|
|
150
153
|
function preferLimitsPlan(purchase, limits) {
|
|
151
154
|
const limitsRef = limits?.planRef;
|
|
152
155
|
if (!limitsRef) return false;
|
|
@@ -161,6 +164,12 @@ function balanceSummary(customer) {
|
|
|
161
164
|
if (!row) return null;
|
|
162
165
|
return row.replace(/^Balance:\s*/, "");
|
|
163
166
|
}
|
|
167
|
+
function identityRow(customer) {
|
|
168
|
+
const ref = customer?.ref?.trim();
|
|
169
|
+
if (!ref) return null;
|
|
170
|
+
const email = customer?.email?.trim();
|
|
171
|
+
return email ? `Signed in as: ${email} \xB7 ${ref}` : `Signed in as: ${ref}`;
|
|
172
|
+
}
|
|
164
173
|
function balanceRow(customer) {
|
|
165
174
|
if (!customer?.balance) return null;
|
|
166
175
|
const credits = customer.balance.credits ?? 0;
|
|
@@ -502,8 +511,7 @@ function narrateAccountBody(input) {
|
|
|
502
511
|
} else {
|
|
503
512
|
position = "After your first call";
|
|
504
513
|
}
|
|
505
|
-
|
|
506
|
-
return `${product} is on ${planName}${priceBit}. ${position}. ` + (creditsUnused ? `Credits are not used on this plan. Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.` : `Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.`);
|
|
514
|
+
return `${product} is on ${planName}${priceBit}. ${position}. ` + (creditsUnused(customer) ? `Credits are not used on this plan. Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.` : `Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch.`);
|
|
507
515
|
}
|
|
508
516
|
if (state === "E") {
|
|
509
517
|
const left2 = remainingOfTotal(usage);
|
|
@@ -526,7 +534,7 @@ function narrateAccountBody(input) {
|
|
|
526
534
|
return otherShape !== "free" && otherShape !== "trial";
|
|
527
535
|
});
|
|
528
536
|
const carryOn = others.length > 0 ? ` ${joinOr(others.map((item) => carryOnFragment(item, customer)))}.` : "";
|
|
529
|
-
const antiTrap =
|
|
537
|
+
const antiTrap = creditsUnused(customer) ? ` Adding credits will not help, because ${planName} does not spend them.` : "";
|
|
530
538
|
const priceBit = planPriceBit(plan);
|
|
531
539
|
return `${product} is on ${planName}${priceBit}. ${capBit}.${consequence}.${antiTrap}${carryOn} Call \`${VIEWER_TOOL_NAME}\` with view: 'checkout' to switch plan.`;
|
|
532
540
|
}
|
|
@@ -613,6 +621,8 @@ function narrateManageAccount(data, options) {
|
|
|
613
621
|
now
|
|
614
622
|
})
|
|
615
623
|
);
|
|
624
|
+
const identity = identityRow(customer);
|
|
625
|
+
if (identity) lines.push(identity);
|
|
616
626
|
if (state === "A" || state === "H") {
|
|
617
627
|
const bal = balanceRow(customer);
|
|
618
628
|
if (bal) lines.push(bal);
|
|
@@ -813,6 +823,15 @@ function enrichPurchase(purchase) {
|
|
|
813
823
|
...enrichedPlanSnapshot !== void 0 ? { planSnapshot: enrichedPlanSnapshot } : {}
|
|
814
824
|
};
|
|
815
825
|
}
|
|
826
|
+
function signedInCustomerRefOnly(getCustomerRef) {
|
|
827
|
+
return (extra) => {
|
|
828
|
+
const ref = getCustomerRef(extra);
|
|
829
|
+
if (typeof ref !== "string") return null;
|
|
830
|
+
const trimmed = ref.trim();
|
|
831
|
+
if (!trimmed || trimmed === "anonymous") return null;
|
|
832
|
+
return trimmed;
|
|
833
|
+
};
|
|
834
|
+
}
|
|
816
835
|
function defaultGetCustomerRef(extra) {
|
|
817
836
|
const candidates = [
|
|
818
837
|
extra?.http?.authInfo?.extra?.customer_ref,
|
|
@@ -945,13 +964,9 @@ var VIEW_PRIORITY = ["checkout", "topup", "account"];
|
|
|
945
964
|
function hasActivePlan(customer, productRef) {
|
|
946
965
|
return selectActivePlanPurchase(customer?.purchase?.purchases, productRef) !== null;
|
|
947
966
|
}
|
|
948
|
-
function isOutOfCredits(customer) {
|
|
949
|
-
const credits = customer?.balance?.credits;
|
|
950
|
-
return credits === 0;
|
|
951
|
-
}
|
|
952
967
|
function deriveDefaultView(data, enabledViews = new Set(SOLVAPAY_MCP_ADVERTISED_VIEW_KINDS)) {
|
|
953
968
|
const customer = data.customer;
|
|
954
|
-
const preferred = !hasActivePlan(customer, data.productRef) ? "checkout" :
|
|
969
|
+
const preferred = !hasActivePlan(customer, data.productRef) ? "checkout" : customer?.nextAction === "topup" ? "topup" : "account";
|
|
955
970
|
if (enabledViews.has(preferred)) return preferred;
|
|
956
971
|
for (const view of VIEW_PRIORITY) {
|
|
957
972
|
if (enabledViews.has(view)) return view;
|
|
@@ -1193,12 +1208,10 @@ var createBootstrapProductError = (productResult) => {
|
|
|
1193
1208
|
return err;
|
|
1194
1209
|
};
|
|
1195
1210
|
function createBuildBootstrapPayload(options) {
|
|
1196
|
-
const {
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
getCustomerRef = defaultGetCustomerRef
|
|
1201
|
-
} = options;
|
|
1211
|
+
const { solvaPay, productRef, publicBaseUrl } = options;
|
|
1212
|
+
const getCustomerRef = signedInCustomerRefOnly(
|
|
1213
|
+
options.getCustomerRef ?? defaultGetCustomerRef
|
|
1214
|
+
);
|
|
1202
1215
|
const fetchPublishableKey = async () => {
|
|
1203
1216
|
try {
|
|
1204
1217
|
const platform = await solvaPay.apiClient.getPlatformConfig?.();
|
|
@@ -1253,18 +1266,16 @@ function createBuildBootstrapPayload(options) {
|
|
|
1253
1266
|
}
|
|
1254
1267
|
}
|
|
1255
1268
|
const checkoutPurpose = view === "topup" || view !== "checkout" && view !== "auto-recharge" && limits?.paywallReason === "topup_required" ? "credit_topup" : void 0;
|
|
1256
|
-
const checkoutResult = await wrapError(
|
|
1269
|
+
const checkoutResult = customerRef ? await wrapError(
|
|
1257
1270
|
createMcpCheckoutSession(
|
|
1258
|
-
|
|
1259
|
-
getCustomerRef: () => customerRef ?? "anonymous"
|
|
1260
|
-
}),
|
|
1271
|
+
buildRequest(extra),
|
|
1261
1272
|
{
|
|
1262
1273
|
productRef,
|
|
1263
1274
|
...checkoutPurpose ? { purpose: checkoutPurpose } : {}
|
|
1264
1275
|
},
|
|
1265
1276
|
{ solvaPay }
|
|
1266
1277
|
)
|
|
1267
|
-
);
|
|
1278
|
+
) : await unauthenticated();
|
|
1268
1279
|
if (isErrorResult(merchantResult)) {
|
|
1269
1280
|
throw createBootstrapMerchantError(merchantResult);
|
|
1270
1281
|
}
|
|
@@ -1287,19 +1298,16 @@ function createBuildBootstrapPayload(options) {
|
|
|
1287
1298
|
} : null;
|
|
1288
1299
|
const activePurchase2 = selectActivePlanPurchase(enrichedPurchase?.purchases, productRef);
|
|
1289
1300
|
const usage = customerRef ? deriveUsageSnapshot({
|
|
1290
|
-
// Consumption comes from `limits.used` (or `limit - remaining`)
|
|
1291
|
-
// inside `deriveUsageSnapshot`. `purchase.usage.used` is only
|
|
1292
|
-
// ever reset to zero by the backend and must not be the source.
|
|
1293
|
-
used: 0,
|
|
1294
1301
|
periodStart: activePurchase2?.usage?.periodStart,
|
|
1295
1302
|
periodEnd: activePurchase2?.usage?.periodEnd,
|
|
1296
1303
|
purchaseRef: activePurchase2?.reference,
|
|
1297
1304
|
limits
|
|
1298
1305
|
}) : null;
|
|
1299
1306
|
const signals = limits ? creditSignals2(limits) : null;
|
|
1300
|
-
const state = limits ? classifyPaywallState(limits) : null;
|
|
1301
1307
|
const customer = customerRef ? {
|
|
1302
1308
|
ref: customerRef,
|
|
1309
|
+
email: purchase?.email?.trim() ? purchase.email.trim() : null,
|
|
1310
|
+
name: purchase?.name?.trim() ? purchase.name.trim() : null,
|
|
1303
1311
|
purchase: enrichedPurchase,
|
|
1304
1312
|
paymentMethod: okOrNull(paymentMethodResult),
|
|
1305
1313
|
balance: okOrNull(balanceResult),
|
|
@@ -1309,8 +1317,9 @@ function createBuildBootstrapPayload(options) {
|
|
|
1309
1317
|
...signals?.remainingCalls !== void 0 ? { remainingCalls: signals.remainingCalls } : {},
|
|
1310
1318
|
...signals?.creditsPerCall !== void 0 ? { creditsPerCall: signals.creditsPerCall } : {},
|
|
1311
1319
|
...signals?.shortfallCredits !== void 0 ? { shortfallCredits: signals.shortfallCredits } : {},
|
|
1320
|
+
...signals ? { isCreditBased: signals.isCreditBased } : {},
|
|
1312
1321
|
...limits?.autoRecharge !== void 0 ? { autoRecharge: limits.autoRecharge } : {},
|
|
1313
|
-
...
|
|
1322
|
+
...limits?.withinLimits === false ? { nextAction: nextActionFor(classifyPaywallState(limits)) } : {}
|
|
1314
1323
|
} : null;
|
|
1315
1324
|
const checkout = okOrNull(checkoutResult);
|
|
1316
1325
|
const portal = okOrNull(portalResult);
|
|
@@ -1421,11 +1430,13 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1421
1430
|
views = DEFAULT_VIEWS,
|
|
1422
1431
|
csp,
|
|
1423
1432
|
apiBaseUrl,
|
|
1424
|
-
getCustomerRef = defaultGetCustomerRef,
|
|
1425
1433
|
onToolCall,
|
|
1426
1434
|
onToolResult,
|
|
1427
1435
|
branding
|
|
1428
1436
|
} = options;
|
|
1437
|
+
const getCustomerRef = signedInCustomerRefOnly(
|
|
1438
|
+
options.getCustomerRef ?? defaultGetCustomerRef
|
|
1439
|
+
);
|
|
1429
1440
|
const toolIcons = deriveIcons(branding);
|
|
1430
1441
|
if (!/^https?:\/\//i.test(publicBaseUrl)) {
|
|
1431
1442
|
throw new Error(
|
|
@@ -1503,6 +1514,11 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1503
1514
|
publicBaseUrl,
|
|
1504
1515
|
getCustomerRef
|
|
1505
1516
|
});
|
|
1517
|
+
const buildDefaultBootstrapPayload = async (extra) => {
|
|
1518
|
+
const data = await buildBootstrapPayload("account", extra);
|
|
1519
|
+
data.view = deriveDefaultView(data, enabledViews);
|
|
1520
|
+
return data;
|
|
1521
|
+
};
|
|
1506
1522
|
const advertisedViewList = SOLVAPAY_MCP_ADVERTISED_VIEW_KINDS.filter(
|
|
1507
1523
|
(view) => enabledViews.has(view)
|
|
1508
1524
|
);
|
|
@@ -1532,10 +1548,9 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1532
1548
|
});
|
|
1533
1549
|
}
|
|
1534
1550
|
const mode = parseMode(args.mode);
|
|
1535
|
-
const data = await buildBootstrapPayload(requested
|
|
1536
|
-
|
|
1537
|
-
data.view
|
|
1538
|
-
return narratedToolResult(view, data, mode, {
|
|
1551
|
+
const data = requested === void 0 ? await buildDefaultBootstrapPayload(extra) : await buildBootstrapPayload(requested, extra);
|
|
1552
|
+
if (requested !== void 0) data.view = requested;
|
|
1553
|
+
return narratedToolResult(data.view, data, mode, {
|
|
1539
1554
|
...toolMeta,
|
|
1540
1555
|
"openai/widgetSessionId": crypto.randomUUID()
|
|
1541
1556
|
});
|
|
@@ -1892,10 +1907,11 @@ function buildSolvaPayDescriptors(options) {
|
|
|
1892
1907
|
title: "SolvaPay bootstrap",
|
|
1893
1908
|
description: "Current merchant/product/plans/customer snapshot for the embedded UI. Widgets read this idempotently when the host scrubs structuredContent from tool results.",
|
|
1894
1909
|
mimeType: SOLVAPAY_BOOTSTRAP_MIME_TYPE,
|
|
1895
|
-
// View is
|
|
1896
|
-
//
|
|
1897
|
-
//
|
|
1898
|
-
|
|
1910
|
+
// View is the server's default for this customer (checkout with no
|
|
1911
|
+
// active plan, topup on a top-up gate, account otherwise) — the same
|
|
1912
|
+
// choice the viewer makes when `view` is omitted. The widget trusts
|
|
1913
|
+
// this field; it does not resolve the surface from host context.
|
|
1914
|
+
readPayload: (extra) => buildDefaultBootstrapPayload(extra)
|
|
1899
1915
|
};
|
|
1900
1916
|
return { tools, resource, prompts, docsResources, bootstrapResource, buildBootstrapPayload };
|
|
1901
1917
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solvapay/mcp-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Framework-neutral MCP contracts for the SolvaPay SDK (tool names, descriptors, payable handler, paywall meta, CSP, bootstrap payload, OAuth discovery JSON builders, bearer/JWT helpers).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
42
|
"vitest": "^4.1.2",
|
|
43
43
|
"zod": "^4.3.6",
|
|
44
|
-
"@solvapay/server": "2.
|
|
44
|
+
"@solvapay/server": "2.10.0",
|
|
45
45
|
"@solvapay/test-utils": "^0.0.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|