@solvapay/mcp-core 0.1.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/index.cjs ADDED
@@ -0,0 +1,1397 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DEFAULT_OAUTH_PATHS: () => DEFAULT_OAUTH_PATHS,
34
+ MCP_TOOL_NAMES: () => MCP_TOOL_NAMES,
35
+ McpBearerAuthError: () => McpBearerAuthError,
36
+ NARRATORS: () => NARRATORS,
37
+ OPEN_TOOL_FOR_VIEW: () => OPEN_TOOL_FOR_VIEW,
38
+ SOLVAPAY_DEFAULT_CSP: () => SOLVAPAY_DEFAULT_CSP,
39
+ SOLVAPAY_MCP_VIEW_KINDS: () => SOLVAPAY_MCP_VIEW_KINDS,
40
+ SOLVAPAY_OVERVIEW_MARKDOWN: () => SOLVAPAY_OVERVIEW_MARKDOWN,
41
+ SOLVAPAY_OVERVIEW_MIME_TYPE: () => SOLVAPAY_OVERVIEW_MIME_TYPE,
42
+ SOLVAPAY_OVERVIEW_URI: () => SOLVAPAY_OVERVIEW_URI,
43
+ TOOL_FOR_VIEW: () => TOOL_FOR_VIEW,
44
+ VIEW_FOR_OPEN_TOOL: () => VIEW_FOR_OPEN_TOOL,
45
+ VIEW_FOR_TOOL: () => VIEW_FOR_TOOL,
46
+ balanceSummary: () => balanceSummary,
47
+ buildAuthInfoFromBearer: () => buildAuthInfoFromBearer,
48
+ buildPayableHandler: () => buildPayableHandler,
49
+ buildPaywallUiMeta: () => buildPaywallUiMeta,
50
+ buildSolvaPayDescriptors: () => buildSolvaPayDescriptors,
51
+ buildSolvaPayPrompts: () => buildSolvaPayPrompts,
52
+ buildSolvaPayRequest: () => buildSolvaPayRequest,
53
+ createBuildBootstrapPayload: () => createBuildBootstrapPayload,
54
+ decodeJwtPayload: () => decodeJwtPayload,
55
+ defaultGetCustomerRef: () => defaultGetCustomerRef,
56
+ enrichPurchase: () => enrichPurchase,
57
+ extractBearerToken: () => extractBearerToken,
58
+ getCustomerRefFromBearerAuthHeader: () => getCustomerRefFromBearerAuthHeader,
59
+ getCustomerRefFromJwtPayload: () => getCustomerRefFromJwtPayload,
60
+ getOAuthAuthorizationServerResponse: () => getOAuthAuthorizationServerResponse,
61
+ getOAuthProtectedResourceResponse: () => getOAuthProtectedResourceResponse,
62
+ mergeCsp: () => mergeCsp,
63
+ narrateActivatePlan: () => narrateActivatePlan,
64
+ narrateManageAccount: () => narrateManageAccount,
65
+ narrateTopup: () => narrateTopup,
66
+ narrateUpgrade: () => narrateUpgrade,
67
+ narratedToolResult: () => narratedToolResult,
68
+ parseMode: () => parseMode,
69
+ paywallToolResult: () => paywallToolResult,
70
+ previewJson: () => previewJson,
71
+ resolveOAuthPaths: () => resolveOAuthPaths,
72
+ toolErrorResult: () => toolErrorResult,
73
+ toolResult: () => toolResult,
74
+ uiPlaceholder: () => uiPlaceholder,
75
+ withoutTrailingSlash: () => withoutTrailingSlash
76
+ });
77
+ module.exports = __toCommonJS(index_exports);
78
+
79
+ // src/tool-names.ts
80
+ var MCP_TOOL_NAMES = {
81
+ createPayment: "create_payment_intent",
82
+ processPayment: "process_payment",
83
+ createTopupPayment: "create_topup_payment_intent",
84
+ cancelRenewal: "cancel_renewal",
85
+ reactivateRenewal: "reactivate_renewal",
86
+ activatePlan: "activate_plan",
87
+ createCheckoutSession: "create_checkout_session",
88
+ createCustomerSession: "create_customer_session",
89
+ upgrade: "upgrade",
90
+ manageAccount: "manage_account",
91
+ topup: "topup"
92
+ };
93
+
94
+ // src/types.ts
95
+ var SOLVAPAY_MCP_VIEW_KINDS = [
96
+ "checkout",
97
+ "account",
98
+ "topup",
99
+ "paywall",
100
+ "nudge"
101
+ ];
102
+ var TOOL_FOR_VIEW = {
103
+ checkout: "upgrade",
104
+ account: "manage_account",
105
+ topup: "topup"
106
+ };
107
+ var VIEW_FOR_TOOL = Object.fromEntries(
108
+ Object.entries(TOOL_FOR_VIEW).map(([view, tool]) => [
109
+ tool,
110
+ view
111
+ ])
112
+ );
113
+ var OPEN_TOOL_FOR_VIEW = TOOL_FOR_VIEW;
114
+ var VIEW_FOR_OPEN_TOOL = VIEW_FOR_TOOL;
115
+
116
+ // src/narrate.ts
117
+ var ZERO_DECIMAL = /* @__PURE__ */ new Set(["bif", "clp", "djf", "gnf", "jpy", "kmf", "krw", "mga", "pyg", "rwf", "ugx", "vnd", "vuv", "xaf", "xof", "xpf"]);
118
+ function formatMoney(amountMinor, currency) {
119
+ if (amountMinor == null || !currency) return null;
120
+ const zero = ZERO_DECIMAL.has(currency.toLowerCase());
121
+ const major = zero ? amountMinor : amountMinor / 100;
122
+ try {
123
+ return new Intl.NumberFormat("en-US", {
124
+ style: "currency",
125
+ currency: currency.toUpperCase(),
126
+ maximumFractionDigits: zero ? 0 : 2
127
+ }).format(major);
128
+ } catch {
129
+ return `${currency.toUpperCase()} ${major.toFixed(zero ? 0 : 2)}`;
130
+ }
131
+ }
132
+ function formatDate(iso) {
133
+ if (!iso) return null;
134
+ try {
135
+ return new Date(iso).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ function activePurchase(customer) {
141
+ const list = customer?.purchase?.purchases ?? [];
142
+ return list[0] ?? null;
143
+ }
144
+ function productName(data) {
145
+ const name = data.product?.name;
146
+ return typeof name === "string" && name ? name : "SolvaPay";
147
+ }
148
+ function balanceSummary(customer) {
149
+ const row = balanceRow(customer);
150
+ if (!row) return null;
151
+ return row.replace(/^Balance:\s*/, "");
152
+ }
153
+ function balanceRow(customer) {
154
+ if (!customer?.balance) return null;
155
+ const credits = customer.balance.credits ?? 0;
156
+ if (!credits && credits !== 0) return null;
157
+ const currency = customer.balance.displayCurrency;
158
+ const rate = customer.balance.displayExchangeRate ?? 1;
159
+ const majorMinor = currency ? Math.round(credits / rate) : null;
160
+ const money = formatMoney(majorMinor, currency ?? null);
161
+ const fmt = new Intl.NumberFormat("en-US").format(credits);
162
+ return money ? `Balance: ${fmt} credits (~${money})` : `Balance: ${fmt} credits`;
163
+ }
164
+ function commandsLine(commands) {
165
+ return `Commands: ${commands.map((c) => `\`/${c}\``).join(" ")}`;
166
+ }
167
+ function plansListLines(plans) {
168
+ return plans.map((p) => {
169
+ const name = p.name ?? "Plan";
170
+ const price = formatMoney(p.price, p.currency);
171
+ const cycle = p.billingCycle ? `/${p.billingCycle}` : "";
172
+ const type = p.planType === "free" ? "no payment required" : p.planType === "usage-based" ? "pay as you go" : p.planType === "trial" ? "trial" : "recurring";
173
+ if (price && p.planType !== "free") {
174
+ return `${name} \xB7 ${type} \xB7 ${price}${cycle}`;
175
+ }
176
+ return `${name} \xB7 ${type}`;
177
+ });
178
+ }
179
+ function hostedPortalLink(data) {
180
+ const url = data.portalUrl;
181
+ if (typeof url === "string" && /^https?:\/\//i.test(url)) {
182
+ return { uri: url, name: "Open hosted portal" };
183
+ }
184
+ return null;
185
+ }
186
+ function narrateManageAccount(data) {
187
+ const lines = [];
188
+ const customer = data.customer;
189
+ const active = activePurchase(customer);
190
+ const name = productName(data);
191
+ if (!active) {
192
+ lines.push(`**Welcome to ${name}**`);
193
+ lines.push("");
194
+ const plans = data.plans ?? [];
195
+ if (plans.length > 0) {
196
+ lines.push("No active plan. Plans available:");
197
+ lines.push(...plansListLines(plans));
198
+ } else {
199
+ lines.push("No active plan.");
200
+ }
201
+ lines.push("");
202
+ lines.push(commandsLine(["activate_plan", "upgrade"]));
203
+ } else {
204
+ lines.push(`**${name} \u2014 your account**`);
205
+ lines.push("");
206
+ const plan = active.planSnapshot;
207
+ if (plan) {
208
+ const planName = plan.name ?? "Plan";
209
+ const price = formatMoney(plan.price, plan.currency);
210
+ const cycle = plan.billingCycle ? `/${plan.billingCycle}` : "";
211
+ const end = formatDate(active.endDate);
212
+ const parts = [planName];
213
+ if (price && plan.planType !== "free") parts.push(`${price}${cycle}`);
214
+ if (end) parts.push(`renews ${end}`);
215
+ lines.push(`Plan: ${parts.join(" \xB7 ")}`);
216
+ }
217
+ const bal = balanceRow(customer);
218
+ if (bal) lines.push(bal);
219
+ lines.push("");
220
+ lines.push(commandsLine(["topup", "upgrade"]));
221
+ }
222
+ const links = [];
223
+ const portal = hostedPortalLink(data);
224
+ if (portal) links.push(portal);
225
+ return { text: lines.join("\n"), links };
226
+ }
227
+ function narrateUpgrade(data) {
228
+ const lines = [];
229
+ lines.push(`**Upgrade \u2014 ${productName(data)}**`);
230
+ lines.push("");
231
+ const plans = (data.plans ?? []).filter((p) => p.planType !== "free");
232
+ if (plans.length > 0) {
233
+ lines.push("Plans available:");
234
+ lines.push(...plansListLines(plans));
235
+ } else {
236
+ lines.push("No paid plans are configured on this product yet.");
237
+ }
238
+ lines.push("");
239
+ lines.push(commandsLine(["manage_account", "topup"]));
240
+ return { text: lines.join("\n") };
241
+ }
242
+ function narrateTopup(data) {
243
+ const lines = [];
244
+ lines.push(`**Top up \u2014 ${productName(data)}**`);
245
+ lines.push("");
246
+ const bal = balanceRow(data.customer);
247
+ if (bal) lines.push(bal);
248
+ const currency = data.customer?.balance?.displayCurrency ?? "USD";
249
+ const presets = [1e3, 2500, 5e3, 1e4].map((m) => formatMoney(m, currency)).filter(Boolean).join(" \xB7 ");
250
+ if (presets) lines.push(`Top-up presets: ${presets}`);
251
+ lines.push("");
252
+ lines.push(commandsLine(["manage_account"]));
253
+ return { text: lines.join("\n") };
254
+ }
255
+ function narrateActivatePlan(data) {
256
+ const lines = [];
257
+ lines.push(`**Activate a plan \u2014 ${productName(data)}**`);
258
+ lines.push("");
259
+ const plans = data.plans ?? [];
260
+ if (plans.length > 0) {
261
+ lines.push("Plans available:");
262
+ lines.push(...plansListLines(plans));
263
+ } else {
264
+ lines.push("No plans are configured on this product yet.");
265
+ }
266
+ lines.push("");
267
+ lines.push(commandsLine(["manage_account", "topup"]));
268
+ return { text: lines.join("\n") };
269
+ }
270
+ var NARRATORS = {
271
+ upgrade: narrateUpgrade,
272
+ manage_account: narrateManageAccount,
273
+ topup: narrateTopup,
274
+ activate_plan: narrateActivatePlan
275
+ };
276
+ var UI_OPENED_VERB = {
277
+ topup: (p) => `Opened ${p} top-up.`,
278
+ upgrade: (p) => `Opened ${p} upgrade.`,
279
+ manage_account: (p) => `Opened your ${p} account.`,
280
+ activate_plan: (p) => `Opened ${p} plan picker.`
281
+ };
282
+ function uiPlaceholder(tool, data) {
283
+ const name = productName(data);
284
+ const opened = UI_OPENED_VERB[tool](name);
285
+ const balance = balanceSummary(data.customer);
286
+ const parts = [opened];
287
+ if (balance) parts.push(`Balance: ${balance}.`);
288
+ parts.push("Pass `mode: 'text'` for a markdown summary.");
289
+ return parts.join(" ");
290
+ }
291
+
292
+ // src/helpers.ts
293
+ var ZERO_DECIMAL_CURRENCIES = /* @__PURE__ */ new Set([
294
+ "bif",
295
+ "clp",
296
+ "djf",
297
+ "gnf",
298
+ "jpy",
299
+ "kmf",
300
+ "krw",
301
+ "mga",
302
+ "pyg",
303
+ "rwf",
304
+ "ugx",
305
+ "vnd",
306
+ "vuv",
307
+ "xaf",
308
+ "xof",
309
+ "xpf"
310
+ ]);
311
+ function formatMinorUnits(amountMinor, currency) {
312
+ if (amountMinor == null || !currency) return null;
313
+ const zeroDecimal = ZERO_DECIMAL_CURRENCIES.has(currency.toLowerCase());
314
+ const fractionDigits = zeroDecimal ? 0 : 2;
315
+ const major = zeroDecimal ? amountMinor : amountMinor / 100;
316
+ try {
317
+ return new Intl.NumberFormat("en-US", {
318
+ style: "currency",
319
+ currency: currency.toUpperCase(),
320
+ minimumFractionDigits: fractionDigits,
321
+ maximumFractionDigits: fractionDigits
322
+ }).format(major);
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+ function enrichPurchase(purchase) {
328
+ const amount = typeof purchase.amount === "number" ? purchase.amount : void 0;
329
+ const originalAmount = typeof purchase.originalAmount === "number" ? purchase.originalAmount : void 0;
330
+ const currency = typeof purchase.currency === "string" ? purchase.currency : void 0;
331
+ const priceDisplay = formatMinorUnits(originalAmount, currency) ?? formatMinorUnits(amount, "USD");
332
+ const priceUsdDisplay = currency && currency.toUpperCase() !== "USD" ? formatMinorUnits(amount, "USD") : null;
333
+ const planSnapshot = purchase.planSnapshot;
334
+ const enrichedPlanSnapshot = planSnapshot && typeof planSnapshot === "object" ? (() => {
335
+ const snap = planSnapshot;
336
+ const price = typeof snap.price === "number" ? snap.price : void 0;
337
+ const snapCurrency = typeof snap.currency === "string" ? snap.currency : void 0;
338
+ const snapPriceDisplay = formatMinorUnits(price, snapCurrency);
339
+ return snapPriceDisplay ? { ...snap, priceDisplay: snapPriceDisplay } : snap;
340
+ })() : planSnapshot;
341
+ return {
342
+ ...purchase,
343
+ ...priceDisplay ? { priceDisplay } : {},
344
+ ...priceUsdDisplay ? { priceUsdDisplay } : {},
345
+ ...enrichedPlanSnapshot !== void 0 ? { planSnapshot: enrichedPlanSnapshot } : {}
346
+ };
347
+ }
348
+ function defaultGetCustomerRef(extra) {
349
+ const fromExtra = extra?.authInfo?.extra?.customer_ref;
350
+ if (typeof fromExtra === "string" && fromExtra.trim()) {
351
+ return fromExtra.trim();
352
+ }
353
+ return null;
354
+ }
355
+ function buildSolvaPayRequest(extra, options = {}) {
356
+ const {
357
+ method = "GET",
358
+ query,
359
+ body,
360
+ getCustomerRef = defaultGetCustomerRef,
361
+ origin = "http://solvapay-mcp-server.local/"
362
+ } = options;
363
+ const url = new URL(origin);
364
+ if (query) {
365
+ for (const [key, value] of Object.entries(query)) {
366
+ if (value !== void 0) url.searchParams.set(key, value);
367
+ }
368
+ }
369
+ const headers = new Headers();
370
+ const customerRef = getCustomerRef(extra);
371
+ if (customerRef) {
372
+ headers.set("x-user-id", customerRef);
373
+ }
374
+ const init = { method, headers };
375
+ if (body !== void 0) {
376
+ headers.set("content-type", "application/json");
377
+ init.body = JSON.stringify(body);
378
+ }
379
+ return new Request(url, init);
380
+ }
381
+ function toolResult(data) {
382
+ return {
383
+ content: [{ type: "text", text: JSON.stringify(data) }],
384
+ structuredContent: data
385
+ };
386
+ }
387
+ function parseMode(raw) {
388
+ if (raw === "ui" || raw === "text" || raw === "auto") return raw;
389
+ return "ui";
390
+ }
391
+ function narratedToolResult(tool, data, mode = "ui", baseMeta = void 0) {
392
+ const narrator = NARRATORS[tool];
393
+ if (!narrator) {
394
+ const fallback = toolResult(data);
395
+ if (mode === "text" && baseMeta && "ui" in baseMeta) {
396
+ const { ui: _ui, ...rest } = baseMeta;
397
+ return { ...fallback, _meta: rest };
398
+ }
399
+ return baseMeta ? { ...fallback, _meta: baseMeta } : fallback;
400
+ }
401
+ const { text, links } = narrator(data);
402
+ const narratedBlock = {
403
+ type: "text",
404
+ text,
405
+ annotations: { audience: ["assistant"] }
406
+ };
407
+ const resourceLinkBlocks = (links ?? []).map((l) => ({
408
+ type: "resource_link",
409
+ uri: l.uri,
410
+ name: l.name,
411
+ annotations: { audience: ["user"] }
412
+ // `resource_link` isn't in the structural content union we use for
413
+ // `SolvaPayCallToolResult`, but the official SDK accepts it — we
414
+ // cast at the boundary to keep the local type narrow while still
415
+ // shipping the enrichment.
416
+ }));
417
+ const placeholderBlock = {
418
+ type: "text",
419
+ text: uiPlaceholder(tool, data)
420
+ };
421
+ const content = mode === "ui" ? [placeholderBlock] : mode === "text" ? [narratedBlock, ...resourceLinkBlocks] : [narratedBlock, ...resourceLinkBlocks];
422
+ const meta = mode === "text" && baseMeta && "ui" in baseMeta ? Object.fromEntries(Object.entries(baseMeta).filter(([k]) => k !== "ui")) : baseMeta;
423
+ return {
424
+ content,
425
+ structuredContent: data,
426
+ ...meta ? { _meta: meta } : {}
427
+ };
428
+ }
429
+ function toolErrorResult(error) {
430
+ return {
431
+ isError: true,
432
+ content: [{ type: "text", text: JSON.stringify(error) }],
433
+ structuredContent: error
434
+ };
435
+ }
436
+ function previewJson(value, max = 400) {
437
+ try {
438
+ const json = JSON.stringify(value);
439
+ if (!json) return String(value);
440
+ return json.length > max ? `${json.slice(0, max)}\u2026(+${json.length - max} chars)` : json;
441
+ } catch {
442
+ return String(value);
443
+ }
444
+ }
445
+
446
+ // src/paywall-meta.ts
447
+ function buildPaywallUiMeta(input) {
448
+ return {
449
+ ui: {
450
+ resourceUri: input.resourceUri
451
+ }
452
+ };
453
+ }
454
+
455
+ // src/paywallToolResult.ts
456
+ var import_server = require("@solvapay/server");
457
+ async function paywallToolResult(errOrGate, ctx) {
458
+ const paywallContent = errOrGate instanceof import_server.PaywallError ? errOrGate.structuredContent : errOrGate;
459
+ const narrationText = errOrGate instanceof import_server.PaywallError ? errOrGate.message : paywallContent.message;
460
+ let structuredContent;
461
+ if (ctx.buildBootstrap) {
462
+ const bootstrap = await ctx.buildBootstrap("paywall", ctx.extra, {
463
+ paywall: paywallContent
464
+ });
465
+ structuredContent = bootstrap;
466
+ } else {
467
+ structuredContent = paywallContent;
468
+ }
469
+ return {
470
+ // Deliberately `false`: paywall is a user-actionable gate, not a
471
+ // tool failure. Hosts short-circuit on `isError: true` and render
472
+ // the error text instead of opening the UI resource advertised by
473
+ // `_meta.ui`, which meant the paywall widget never appeared on
474
+ // MCPJam / Claude Desktop / ChatGPT Apps. The `structuredContent`
475
+ // and `content[0].text` still carry the gate reason so the LLM
476
+ // can narrate it, while `_meta.ui` triggers the widget.
477
+ isError: false,
478
+ content: [{ type: "text", text: narrationText }],
479
+ structuredContent,
480
+ _meta: buildPaywallUiMeta({ resourceUri: ctx.resourceUri })
481
+ };
482
+ }
483
+
484
+ // src/csp.ts
485
+ var SOLVAPAY_DEFAULT_CSP = {
486
+ resourceDomains: [
487
+ "https://js.stripe.com",
488
+ "https://*.stripe.com",
489
+ "https://b.stripecdn.com"
490
+ ],
491
+ connectDomains: [
492
+ "https://api.stripe.com",
493
+ "https://m.stripe.com",
494
+ "https://r.stripe.com",
495
+ "https://q.stripe.com",
496
+ "https://errors.stripe.com"
497
+ ],
498
+ frameDomains: ["https://js.stripe.com", "https://hooks.stripe.com"]
499
+ };
500
+ function mergeCsp(overrides) {
501
+ if (!overrides) return SOLVAPAY_DEFAULT_CSP;
502
+ const merge = (base, extra) => extra && extra.length ? Array.from(/* @__PURE__ */ new Set([...base, ...extra])) : base;
503
+ return {
504
+ resourceDomains: merge(SOLVAPAY_DEFAULT_CSP.resourceDomains, overrides.resourceDomains),
505
+ connectDomains: merge(SOLVAPAY_DEFAULT_CSP.connectDomains, overrides.connectDomains),
506
+ frameDomains: merge(SOLVAPAY_DEFAULT_CSP.frameDomains, overrides.frameDomains)
507
+ };
508
+ }
509
+
510
+ // src/descriptors.ts
511
+ var import_server3 = require("@solvapay/server");
512
+ var import_zod = require("zod");
513
+
514
+ // src/bootstrap-payload.ts
515
+ var import_server2 = require("@solvapay/server");
516
+ var okOrNull = (result) => (0, import_server2.isErrorResult)(result) ? null : result;
517
+ function createBuildBootstrapPayload(options) {
518
+ const {
519
+ solvaPay,
520
+ productRef,
521
+ publicBaseUrl,
522
+ getCustomerRef = defaultGetCustomerRef
523
+ } = options;
524
+ const fetchPublishableKey = async () => {
525
+ try {
526
+ const platform = await solvaPay.apiClient.getPlatformConfig?.();
527
+ return platform?.stripePublishableKey ?? null;
528
+ } catch {
529
+ return null;
530
+ }
531
+ };
532
+ const buildRequest = (extra) => buildSolvaPayRequest(extra, { getCustomerRef });
533
+ const productQueryRequest = () => buildSolvaPayRequest(void 0, { query: { productRef }, getCustomerRef: () => null });
534
+ return async (view, extra, extras = {}) => {
535
+ const customerRef = getCustomerRef(extra);
536
+ const wrapError = (promise) => promise.catch((err) => ({
537
+ error: err instanceof Error ? err.message : String(err),
538
+ status: 500
539
+ }));
540
+ const unauthenticated = () => Promise.resolve({ error: "unauthenticated", status: 401 });
541
+ const [
542
+ stripePublishableKey,
543
+ merchantResult,
544
+ productResult,
545
+ plansResult,
546
+ purchaseResult,
547
+ paymentMethodResult,
548
+ balanceResult,
549
+ usageResult
550
+ ] = await Promise.all([
551
+ fetchPublishableKey(),
552
+ (0, import_server2.getMerchantCore)(buildRequest(void 0), { solvaPay }),
553
+ (0, import_server2.getProductCore)(productQueryRequest(), { solvaPay }),
554
+ wrapError((0, import_server2.listPlansCore)(productQueryRequest(), { solvaPay })),
555
+ customerRef ? wrapError((0, import_server2.checkPurchaseCore)(buildRequest(extra), { solvaPay })) : unauthenticated(),
556
+ customerRef ? wrapError((0, import_server2.getPaymentMethodCore)(buildRequest(extra), { solvaPay })) : unauthenticated(),
557
+ customerRef ? wrapError((0, import_server2.getCustomerBalanceCore)(buildRequest(extra), { solvaPay })) : unauthenticated(),
558
+ customerRef ? wrapError((0, import_server2.getUsageCore)(buildRequest(extra), { solvaPay })) : unauthenticated()
559
+ ]);
560
+ if ((0, import_server2.isErrorResult)(merchantResult)) {
561
+ throw new Error(`bootstrap: merchant lookup failed: ${merchantResult.error}`);
562
+ }
563
+ if ((0, import_server2.isErrorResult)(productResult)) {
564
+ throw new Error(`bootstrap: product lookup failed: ${productResult.error}`);
565
+ }
566
+ const plans = (0, import_server2.isErrorResult)(plansResult) ? [] : plansResult.plans;
567
+ const purchase = okOrNull(purchaseResult);
568
+ const enrichedPurchase = purchase ? {
569
+ ...purchase,
570
+ purchases: purchase.purchases.map(
571
+ (p) => enrichPurchase(p)
572
+ )
573
+ } : null;
574
+ const customer = customerRef ? {
575
+ ref: customerRef,
576
+ purchase: enrichedPurchase,
577
+ paymentMethod: okOrNull(paymentMethodResult),
578
+ balance: okOrNull(balanceResult),
579
+ usage: okOrNull(usageResult)
580
+ } : null;
581
+ const payload = {
582
+ view,
583
+ productRef,
584
+ stripePublishableKey,
585
+ returnUrl: publicBaseUrl,
586
+ merchant: merchantResult,
587
+ product: productResult,
588
+ plans,
589
+ customer
590
+ };
591
+ if (extras.paywall) payload.paywall = extras.paywall;
592
+ return payload;
593
+ };
594
+ }
595
+
596
+ // src/resources/overview.ts
597
+ var SOLVAPAY_OVERVIEW_URI = "docs://solvapay/overview.md";
598
+ var SOLVAPAY_OVERVIEW_MIME_TYPE = "text/markdown";
599
+ var SOLVAPAY_OVERVIEW_MARKDOWN = `# SolvaPay MCP server \u2014 overview
600
+
601
+ This MCP server connects a SolvaPay-protected product to your host so users can
602
+ manage their plan, usage, and billing **without leaving the chat**. It is
603
+ dual-audience: every tool returns a UI bootstrap for hosts that render MCP UI
604
+ resources (\`basic-host\`, Claude Desktop, ChatGPT, etc.) and a markdown summary
605
+ with clickable URLs for text-only hosts.
606
+
607
+ ## What the user can do
608
+
609
+ - **Upgrade** \u2014 start or change a paid plan. \`/upgrade\` opens the embedded
610
+ checkout (Stripe Elements inline) on UI hosts and returns a hosted-checkout
611
+ URL on text hosts.
612
+ - **Manage account** \u2014 current plan, balance, usage, payment method, cancel
613
+ or reactivate auto-renewal. Credits + usage are folded inline into the
614
+ account view. \`/manage_account\`.
615
+ - **Top up credits** \u2014 add SolvaPay credits for usage-based plans.
616
+ \`/topup\`.
617
+ - **Activate plan** \u2014 pick a plan from the list or activate a specific plan
618
+ by \`planRef\`. Free plans activate immediately, usage-based plans activate
619
+ when balance covers the configured usage, paid plans return the embedded
620
+ checkout or a hosted-checkout URL. \`/activate_plan\`.
621
+
622
+ ## How it fits together
623
+
624
+ Each intent tool returns a full \`BootstrapPayload\` (merchant + product + plans
625
+ + customer snapshot) so the embedded UI never fires per-view read calls. When
626
+ a paywalled data tool hits the usage limit, its response carries the same
627
+ payload with \`view: "paywall"\`, letting the host open the UI directly on the
628
+ paywall screen \u2014 no second tool call required.
629
+
630
+ Auth is handled by the SolvaPay OAuth bridge (see \`createMcpOAuthBridge\`). The
631
+ bridge injects \`customer_ref\` onto every authenticated request; tools that
632
+ need an authenticated caller return \`Unauthorized\` when it is missing.
633
+
634
+ ## Also see
635
+
636
+ - \`docs://solvapay/overview.md\` (this resource) \u2014 agent-facing narration.
637
+ - \`ui://<app>/mcp-app.html\` \u2014 the embedded UI shell.
638
+ - \`tools/list\` + \`prompts/list\` \u2014 programmatic discovery of the intent tools
639
+ and their slash-command shortcuts.
640
+ - Documentation: https://docs.solvapay.com/sdks/typescript/guides/mcp-app.
641
+ `;
642
+
643
+ // src/descriptors.ts
644
+ function deriveIcons(branding) {
645
+ if (!branding) return void 0;
646
+ const assets = [];
647
+ if (branding.iconUrl) {
648
+ assets.push({ src: branding.iconUrl, sizes: ["any", "512x512"] });
649
+ } else if (branding.logoUrl) {
650
+ assets.push({ src: branding.logoUrl });
651
+ }
652
+ return assets.length > 0 ? assets : void 0;
653
+ }
654
+ var solvapayTool = (hints) => ({ openWorldHint: true, ...hints });
655
+ var INTENT_TOOL_ANNOTATIONS = {
656
+ account: solvapayTool({ readOnlyHint: true, idempotentHint: true }),
657
+ topup: solvapayTool({ destructiveHint: true }),
658
+ checkout: solvapayTool({ destructiveHint: true })
659
+ };
660
+ var DEFAULT_VIEWS = [...SOLVAPAY_MCP_VIEW_KINDS];
661
+ function buildSolvaPayDescriptors(options) {
662
+ const {
663
+ solvaPay,
664
+ productRef,
665
+ resourceUri,
666
+ htmlPath,
667
+ readHtml,
668
+ publicBaseUrl,
669
+ views = DEFAULT_VIEWS,
670
+ csp,
671
+ getCustomerRef = defaultGetCustomerRef,
672
+ onToolCall,
673
+ onToolResult,
674
+ branding
675
+ } = options;
676
+ const toolIcons = deriveIcons(branding);
677
+ if (!/^https?:\/\//i.test(publicBaseUrl)) {
678
+ throw new Error(
679
+ "buildSolvaPayDescriptors: publicBaseUrl must be an http(s) URL (Stripe confirmPayment rejects `ui://`)."
680
+ );
681
+ }
682
+ if (!htmlPath && !readHtml) {
683
+ throw new Error(
684
+ "buildSolvaPayDescriptors: either `htmlPath` (node) or `readHtml` (edge) must be provided."
685
+ );
686
+ }
687
+ const toolMeta = { ui: { resourceUri } };
688
+ const uiToolMeta = { ...toolMeta, audience: "ui" };
689
+ const enabledViews = new Set(views);
690
+ const tools = [];
691
+ const pushTool = (descriptor) => {
692
+ tools.push(toolIcons ? { ...descriptor, icons: toolIcons } : descriptor);
693
+ };
694
+ const UI_ONLY_PREFIX = "UI-only; agents should prefer `upgrade` / `manage_account` / `activate_plan`. ";
695
+ const buildRequest = (extra, init = {}) => buildSolvaPayRequest(extra, { ...init, getCustomerRef });
696
+ const requireCustomerRef = (extra) => {
697
+ const ref = getCustomerRef(extra);
698
+ if (!ref) {
699
+ return toolErrorResult({
700
+ error: "Unauthorized",
701
+ status: 401,
702
+ details: "customer_ref missing from MCP auth context"
703
+ });
704
+ }
705
+ return ref;
706
+ };
707
+ const trace = async (name, args, extra, handler) => {
708
+ const started = Date.now();
709
+ onToolCall?.(name, args, extra);
710
+ try {
711
+ const result = await handler();
712
+ if (onToolResult) onToolResult(name, result, { durationMs: Date.now() - started });
713
+ return result;
714
+ } catch (err) {
715
+ const errorResult = toolErrorResult({
716
+ error: err instanceof Error ? err.message : String(err),
717
+ status: 500,
718
+ details: previewJson(err)
719
+ });
720
+ if (onToolResult) onToolResult(name, errorResult, { durationMs: Date.now() - started });
721
+ return errorResult;
722
+ }
723
+ };
724
+ const buildBootstrapPayload = createBuildBootstrapPayload({
725
+ solvaPay,
726
+ productRef,
727
+ publicBaseUrl,
728
+ getCustomerRef
729
+ });
730
+ const pushIntentTool = (view, title, description) => {
731
+ if (!enabledViews.has(view)) return;
732
+ const name = TOOL_FOR_VIEW[view];
733
+ pushTool({
734
+ name,
735
+ title,
736
+ description,
737
+ // Every intent tool accepts an optional `mode` so users /
738
+ // agents on any host can opt into text-only responses (or
739
+ // suppress the narrated markdown when they know the host is
740
+ // rendering the UI iframe). Default `'auto'` emits both.
741
+ inputSchema: { mode: import_zod.z.enum(["ui", "text", "auto"]).optional() },
742
+ meta: toolMeta,
743
+ annotations: INTENT_TOOL_ANNOTATIONS[view],
744
+ handler: async (args, extra) => trace(name, args, extra, async () => {
745
+ const mode = parseMode(args.mode);
746
+ const data = await buildBootstrapPayload(view, extra);
747
+ return narratedToolResult(name, data, mode, toolMeta);
748
+ })
749
+ });
750
+ };
751
+ const MODE_HINT = " By default renders the UI iframe with a one-line placeholder; pass `mode: 'text'` for a markdown-only summary on CLI / text-only hosts, or `mode: 'auto'` to include both.";
752
+ pushIntentTool(
753
+ "checkout",
754
+ "Upgrade plan",
755
+ "Start or change a paid plan for the current customer. On UI hosts this opens the embedded checkout; on text hosts returns a markdown summary with a checkout URL. Also available: manage_account (current plan + cancel/reactivate), activate_plan (pick or activate a specific plan), topup (add credits)." + MODE_HINT
756
+ );
757
+ pushIntentTool(
758
+ "account",
759
+ "Manage account",
760
+ "Show or manage the current customer's SolvaPay account: plan, balance, usage, payment method, cancel/reactivate auto-renewal. On UI hosts this opens the embedded account view; on text hosts returns a markdown summary. Also available: upgrade (start/change a paid plan), activate_plan (pick or activate), topup (add credits)." + MODE_HINT
761
+ );
762
+ pushIntentTool(
763
+ "topup",
764
+ "Top up credits",
765
+ "Add SolvaPay credits for the current customer. On UI hosts this opens the embedded top-up flow; on text hosts returns a markdown summary with a top-up URL. Also available: manage_account (current plan + balance + usage), upgrade (switch to a recurring plan)." + MODE_HINT
766
+ );
767
+ pushTool({
768
+ name: MCP_TOOL_NAMES.createCheckoutSession,
769
+ description: UI_ONLY_PREFIX + "Create a SolvaPay hosted checkout session and return its URL. The UI opens this URL in a new tab when Stripe Elements is blocked by the host sandbox.",
770
+ inputSchema: {
771
+ planRef: import_zod.z.string().optional(),
772
+ productRef: import_zod.z.string().optional()
773
+ },
774
+ meta: uiToolMeta,
775
+ annotations: solvapayTool({}),
776
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.createCheckoutSession, args, extra, async () => {
777
+ const auth = requireCustomerRef(extra);
778
+ if (typeof auth !== "string") return auth;
779
+ const effectiveProduct = typeof args.productRef === "string" && args.productRef ? args.productRef : productRef;
780
+ const planRef = typeof args.planRef === "string" && args.planRef ? args.planRef : void 0;
781
+ const result = await (0, import_server3.createCheckoutSessionCore)(
782
+ buildRequest(extra, { method: "POST" }),
783
+ { productRef: effectiveProduct, planRef },
784
+ { solvaPay }
785
+ );
786
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
787
+ return toolResult(result);
788
+ })
789
+ });
790
+ pushTool({
791
+ name: MCP_TOOL_NAMES.createPayment,
792
+ description: UI_ONLY_PREFIX + "Create a Stripe payment intent for the authenticated customer to purchase a plan. Returns { clientSecret, publishableKey, accountId?, customerRef } for confirmation with Stripe Elements in the app UI.",
793
+ inputSchema: {
794
+ planRef: import_zod.z.string(),
795
+ productRef: import_zod.z.string()
796
+ },
797
+ meta: uiToolMeta,
798
+ annotations: solvapayTool({}),
799
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.createPayment, args, extra, async () => {
800
+ const auth = requireCustomerRef(extra);
801
+ if (typeof auth !== "string") return auth;
802
+ const planRef = typeof args.planRef === "string" ? args.planRef : "";
803
+ const effectiveProduct = typeof args.productRef === "string" && args.productRef ? args.productRef : productRef;
804
+ const result = await (0, import_server3.createPaymentIntentCore)(
805
+ buildRequest(extra, { method: "POST" }),
806
+ { planRef, productRef: effectiveProduct },
807
+ { solvaPay }
808
+ );
809
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
810
+ return toolResult(result);
811
+ })
812
+ });
813
+ pushTool({
814
+ name: MCP_TOOL_NAMES.processPayment,
815
+ description: UI_ONLY_PREFIX + "Process a Stripe payment intent after client-side confirmation and create the SolvaPay purchase. Call after confirmPayment resolves to short-circuit webhook latency.",
816
+ inputSchema: {
817
+ paymentIntentId: import_zod.z.string(),
818
+ productRef: import_zod.z.string(),
819
+ planRef: import_zod.z.string().optional()
820
+ },
821
+ meta: uiToolMeta,
822
+ annotations: solvapayTool({ destructiveHint: true }),
823
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.processPayment, args, extra, async () => {
824
+ const auth = requireCustomerRef(extra);
825
+ if (typeof auth !== "string") return auth;
826
+ const paymentIntentId = typeof args.paymentIntentId === "string" ? args.paymentIntentId : "";
827
+ const effectiveProduct = typeof args.productRef === "string" && args.productRef ? args.productRef : productRef;
828
+ const planRef = typeof args.planRef === "string" && args.planRef ? args.planRef : void 0;
829
+ const result = await (0, import_server3.processPaymentIntentCore)(
830
+ buildRequest(extra, { method: "POST" }),
831
+ { paymentIntentId, productRef: effectiveProduct, planRef },
832
+ { solvaPay }
833
+ );
834
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
835
+ return toolResult(result);
836
+ })
837
+ });
838
+ pushTool({
839
+ name: MCP_TOOL_NAMES.createCustomerSession,
840
+ description: UI_ONLY_PREFIX + "Create a SolvaPay hosted customer portal session and return its URL. Used to let a paid customer manage or cancel their purchase in a new tab.",
841
+ inputSchema: {},
842
+ meta: uiToolMeta,
843
+ annotations: solvapayTool({ readOnlyHint: true, idempotentHint: true }),
844
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.createCustomerSession, args, extra, async () => {
845
+ const auth = requireCustomerRef(extra);
846
+ if (typeof auth !== "string") return auth;
847
+ const result = await (0, import_server3.createCustomerSessionCore)(buildRequest(extra, { method: "POST" }), {
848
+ solvaPay
849
+ });
850
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
851
+ return toolResult(result);
852
+ })
853
+ });
854
+ pushTool({
855
+ name: MCP_TOOL_NAMES.createTopupPayment,
856
+ description: UI_ONLY_PREFIX + "Create a Stripe payment intent for a credit top-up. Credits are recorded by the SolvaPay webhook after confirmation.",
857
+ inputSchema: {
858
+ amount: import_zod.z.number().int().positive(),
859
+ currency: import_zod.z.string(),
860
+ description: import_zod.z.string().optional()
861
+ },
862
+ meta: uiToolMeta,
863
+ annotations: solvapayTool({}),
864
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.createTopupPayment, args, extra, async () => {
865
+ const auth = requireCustomerRef(extra);
866
+ if (typeof auth !== "string") return auth;
867
+ const amount = typeof args.amount === "number" ? args.amount : 0;
868
+ const currency = typeof args.currency === "string" ? args.currency : "";
869
+ const description = typeof args.description === "string" ? args.description : void 0;
870
+ const result = await (0, import_server3.createTopupPaymentIntentCore)(
871
+ buildRequest(extra, { method: "POST" }),
872
+ { amount, currency, description },
873
+ { solvaPay }
874
+ );
875
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
876
+ return toolResult(result);
877
+ })
878
+ });
879
+ pushTool({
880
+ name: MCP_TOOL_NAMES.cancelRenewal,
881
+ description: UI_ONLY_PREFIX + "Cancel the auto-renewal on an active purchase. Backend keeps access until the current period ends.",
882
+ inputSchema: {
883
+ purchaseRef: import_zod.z.string(),
884
+ reason: import_zod.z.string().optional()
885
+ },
886
+ meta: uiToolMeta,
887
+ annotations: solvapayTool({ destructiveHint: true, idempotentHint: true }),
888
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.cancelRenewal, args, extra, async () => {
889
+ const auth = requireCustomerRef(extra);
890
+ if (typeof auth !== "string") return auth;
891
+ const purchaseRef = typeof args.purchaseRef === "string" ? args.purchaseRef : "";
892
+ const reason = typeof args.reason === "string" ? args.reason : void 0;
893
+ const result = await (0, import_server3.cancelPurchaseCore)(
894
+ buildRequest(extra, { method: "POST" }),
895
+ { purchaseRef, reason },
896
+ { solvaPay }
897
+ );
898
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
899
+ return toolResult(result);
900
+ })
901
+ });
902
+ pushTool({
903
+ name: MCP_TOOL_NAMES.reactivateRenewal,
904
+ description: UI_ONLY_PREFIX + "Undo a pending cancellation so auto-renewal resumes. Only valid while the purchase is still active and its end date hasn't passed.",
905
+ inputSchema: { purchaseRef: import_zod.z.string() },
906
+ meta: uiToolMeta,
907
+ annotations: solvapayTool({ idempotentHint: true }),
908
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.reactivateRenewal, args, extra, async () => {
909
+ const auth = requireCustomerRef(extra);
910
+ if (typeof auth !== "string") return auth;
911
+ const purchaseRef = typeof args.purchaseRef === "string" ? args.purchaseRef : "";
912
+ const result = await (0, import_server3.reactivatePurchaseCore)(
913
+ buildRequest(extra, { method: "POST" }),
914
+ { purchaseRef },
915
+ { solvaPay }
916
+ );
917
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
918
+ return toolResult(result);
919
+ })
920
+ });
921
+ pushTool({
922
+ name: MCP_TOOL_NAMES.activatePlan,
923
+ title: "Activate plan",
924
+ description: "Activate a plan for the current customer. With a `planRef`: free plans activate immediately; usage-based plans activate when the balance covers the configured usage; paid plans return a markdown checkout link on text hosts or open the embedded checkout on UI hosts. Without a `planRef`: returns the available plans so the customer can pick \u2014 UI hosts render the embedded checkout picker, text hosts see a plans list. Also available: upgrade (direct to checkout), manage_account (current plan + usage), topup (add credits)." + MODE_HINT,
925
+ inputSchema: {
926
+ productRef: import_zod.z.string().optional(),
927
+ planRef: import_zod.z.string().optional(),
928
+ mode: import_zod.z.enum(["ui", "text", "auto"]).optional()
929
+ },
930
+ meta: toolMeta,
931
+ annotations: solvapayTool({}),
932
+ handler: async (args, extra) => trace(MCP_TOOL_NAMES.activatePlan, args, extra, async () => {
933
+ const effectiveProduct = typeof args.productRef === "string" && args.productRef ? args.productRef : productRef;
934
+ const planRef = typeof args.planRef === "string" && args.planRef ? args.planRef : void 0;
935
+ const mode = parseMode(args.mode);
936
+ if (!planRef) {
937
+ if (!enabledViews.has("checkout")) {
938
+ return toolErrorResult({
939
+ error: "activate_plan requires a planRef on this server",
940
+ status: 400,
941
+ details: 'The checkout view (where the plan picker lives) is not enabled on this server. Pass `planRef` to activate a specific plan, or re-enable the "checkout" view via the `views` option.'
942
+ });
943
+ }
944
+ return narratedToolResult(
945
+ MCP_TOOL_NAMES.activatePlan,
946
+ await buildBootstrapPayload("checkout", extra),
947
+ mode,
948
+ toolMeta
949
+ );
950
+ }
951
+ const auth = requireCustomerRef(extra);
952
+ if (typeof auth !== "string") return auth;
953
+ const result = await (0, import_server3.activatePlanCore)(
954
+ buildRequest(extra, { method: "POST" }),
955
+ { productRef: effectiveProduct, planRef },
956
+ { solvaPay }
957
+ );
958
+ if ((0, import_server3.isErrorResult)(result)) return toolErrorResult(result);
959
+ return toolResult(result);
960
+ })
961
+ });
962
+ const resolvedCsp = mergeCsp(csp);
963
+ const resource = {
964
+ uri: resourceUri,
965
+ mimeType: "text/html;profile=mcp-app",
966
+ csp: resolvedCsp,
967
+ readHtml: readHtml ? readHtml : async () => {
968
+ const fs = await import("fs/promises");
969
+ return fs.readFile(htmlPath, "utf-8");
970
+ }
971
+ };
972
+ const prompts = buildSolvaPayPrompts({ enabledViews });
973
+ const docsResources = [
974
+ {
975
+ uri: SOLVAPAY_OVERVIEW_URI,
976
+ name: "SolvaPay MCP \u2014 overview",
977
+ title: "SolvaPay overview",
978
+ description: 'Agent-facing "start here" doc \u2014 explains the five intent tools, dual-audience fallback, and auth model before any tool is called.',
979
+ mimeType: SOLVAPAY_OVERVIEW_MIME_TYPE,
980
+ readBody: () => SOLVAPAY_OVERVIEW_MARKDOWN
981
+ }
982
+ ];
983
+ return { tools, resource, prompts, docsResources, buildBootstrapPayload };
984
+ }
985
+ function buildSolvaPayPrompts(options = {}) {
986
+ const enabled = options.enabledViews ?? new Set(DEFAULT_VIEWS);
987
+ const prompts = [];
988
+ const userMessage = (text) => ({
989
+ messages: [{ role: "user", content: { type: "text", text } }]
990
+ });
991
+ if (enabled.has("checkout")) {
992
+ prompts.push({
993
+ name: MCP_TOOL_NAMES.upgrade,
994
+ title: "Upgrade plan",
995
+ description: "Start or change a paid plan for the current customer.",
996
+ argsSchema: { planRef: import_zod.z.string().optional() },
997
+ handler: async ({ planRef }) => userMessage(
998
+ typeof planRef === "string" && planRef ? `Activate plan ${planRef} for me.` : "Show me the upgrade options for my SolvaPay account."
999
+ )
1000
+ });
1001
+ }
1002
+ if (enabled.has("account")) {
1003
+ prompts.push({
1004
+ name: MCP_TOOL_NAMES.manageAccount,
1005
+ title: "Manage account",
1006
+ description: "Show the current plan, balance, payment method, and cancel/reactivate controls for the current customer.",
1007
+ handler: async () => userMessage("Show me my SolvaPay account.")
1008
+ });
1009
+ }
1010
+ if (enabled.has("topup")) {
1011
+ prompts.push({
1012
+ name: MCP_TOOL_NAMES.topup,
1013
+ title: "Top up credits",
1014
+ description: "Add SolvaPay credits to the current customer.",
1015
+ argsSchema: { amount: import_zod.z.string().optional() },
1016
+ handler: async ({ amount }) => userMessage(
1017
+ typeof amount === "string" && amount ? `Top up my SolvaPay credits by ${amount}.` : "I want to top up my SolvaPay credits."
1018
+ )
1019
+ });
1020
+ }
1021
+ if (enabled.has("checkout")) {
1022
+ prompts.push({
1023
+ name: MCP_TOOL_NAMES.activatePlan,
1024
+ title: "Activate plan",
1025
+ description: "Pick a plan to activate, or activate a specific plan by ref.",
1026
+ argsSchema: { planRef: import_zod.z.string().optional() },
1027
+ handler: async ({ planRef }) => userMessage(
1028
+ typeof planRef === "string" && planRef ? `Activate plan ${planRef} on my SolvaPay account.` : "What plans can I activate on my SolvaPay account?"
1029
+ )
1030
+ });
1031
+ }
1032
+ return prompts;
1033
+ }
1034
+
1035
+ // src/payable-handler.ts
1036
+ var import_server5 = require("@solvapay/server");
1037
+
1038
+ // src/response-context.ts
1039
+ var import_server4 = require("@solvapay/server");
1040
+
1041
+ // src/response-envelope.ts
1042
+ function isResponseResult(value) {
1043
+ return typeof value === "object" && value !== null && value.__solvapayResponse === true;
1044
+ }
1045
+ function assertResponseResult(value) {
1046
+ if (!isResponseResult(value)) {
1047
+ throw new Error(
1048
+ "SolvaPay: registerPayable handler returned a raw value. Handlers must return ctx.respond(data, options?). If you believe you did, this is an internal bug \u2014 please file an issue at https://github.com/solvapay/solvapay-sdk/issues."
1049
+ );
1050
+ }
1051
+ return value;
1052
+ }
1053
+ function makeResponseResult(data, options, emittedBlocks) {
1054
+ return {
1055
+ __solvapayResponse: true,
1056
+ data,
1057
+ ...options !== void 0 ? { options } : {},
1058
+ ...emittedBlocks.length > 0 ? { emittedBlocks: [...emittedBlocks] } : {}
1059
+ };
1060
+ }
1061
+
1062
+ // src/response-context.ts
1063
+ function synthesizePlanStub(limits) {
1064
+ if (!limits?.plan) return null;
1065
+ return { reference: limits.plan };
1066
+ }
1067
+ function snapshotFromLimits(params) {
1068
+ const { customerRef, limits, plan, refresh } = params;
1069
+ return {
1070
+ ref: customerRef,
1071
+ balance: limits?.creditBalance ?? 0,
1072
+ remaining: limits?.remaining ?? null,
1073
+ withinLimits: limits?.withinLimits ?? true,
1074
+ plan,
1075
+ fresh: refresh
1076
+ };
1077
+ }
1078
+ function buildResponseContext(params) {
1079
+ const { customerRef, limits, product, bootstrapProduct, solvaPay } = params;
1080
+ const bootstrapPlan = params.bootstrapPlan ?? synthesizePlanStub(limits);
1081
+ const emittedBlocks = [];
1082
+ const abortController = new AbortController();
1083
+ const freshSnapshot = async () => {
1084
+ const freshLimits = await solvaPay.checkLimits({
1085
+ customerRef,
1086
+ productRef: product,
1087
+ meterName: "requests"
1088
+ });
1089
+ return snapshotFromLimits({
1090
+ customerRef,
1091
+ limits: freshLimits,
1092
+ plan: bootstrapPlan,
1093
+ refresh: freshSnapshot
1094
+ });
1095
+ };
1096
+ const customer = snapshotFromLimits({
1097
+ customerRef,
1098
+ limits,
1099
+ plan: bootstrapPlan,
1100
+ refresh: freshSnapshot
1101
+ });
1102
+ const product_ = bootstrapProduct ?? {
1103
+ reference: product,
1104
+ name: product
1105
+ };
1106
+ function respond(data, options) {
1107
+ return makeResponseResult(data, options, emittedBlocks);
1108
+ }
1109
+ function gate(reason) {
1110
+ const message = reason ?? "Payment required";
1111
+ const structuredContent = {
1112
+ kind: "payment_required",
1113
+ product,
1114
+ checkoutUrl: "",
1115
+ message
1116
+ };
1117
+ throw new import_server4.PaywallError(message, structuredContent);
1118
+ }
1119
+ const ctx = {
1120
+ customer,
1121
+ product: product_,
1122
+ respond,
1123
+ gate,
1124
+ // ——————————————————————————————————————————————————————————————
1125
+ // Reserved surface — V1 stubs, V1.1 real.
1126
+ // ——————————————————————————————————————————————————————————————
1127
+ emit: async (block) => {
1128
+ emittedBlocks.push(block);
1129
+ },
1130
+ progress: async () => {
1131
+ },
1132
+ progressRaw: async () => {
1133
+ },
1134
+ signal: abortController.signal
1135
+ };
1136
+ return { ctx, emittedBlocks };
1137
+ }
1138
+
1139
+ // src/payable-handler.ts
1140
+ function buildPayableHandler(solvaPay, ctx, handler) {
1141
+ const { product, resourceUri, buildBootstrap, getCustomerRef } = ctx;
1142
+ const wrappedBusinessLogic = async (args, handlerContext) => {
1143
+ const limits = handlerContext?.limits ?? null;
1144
+ const customerRef = handlerContext?.customerRef ?? "";
1145
+ const { ctx: responseCtx } = buildResponseContext({
1146
+ customerRef,
1147
+ limits,
1148
+ product,
1149
+ solvaPay
1150
+ });
1151
+ return handler(args, responseCtx);
1152
+ };
1153
+ const protectedHandler = solvaPay.payable({ product, getCustomerRef }).mcp(wrappedBusinessLogic);
1154
+ return async (args, extra) => {
1155
+ const result = await protectedHandler(args, extra);
1156
+ if ((0, import_server5.isPaywallStructuredContent)(result.structuredContent)) {
1157
+ const existingMeta = typeof result._meta === "object" && result._meta !== null ? result._meta : {};
1158
+ const gateContent = result.structuredContent;
1159
+ const structuredContent = buildBootstrap ? await buildBootstrap("paywall", extra, {
1160
+ paywall: gateContent
1161
+ }) : result.structuredContent;
1162
+ return {
1163
+ ...result,
1164
+ isError: false,
1165
+ structuredContent,
1166
+ _meta: {
1167
+ ...existingMeta,
1168
+ ...buildPaywallUiMeta({ resourceUri })
1169
+ }
1170
+ };
1171
+ }
1172
+ if (result.isError) {
1173
+ return result;
1174
+ }
1175
+ const envelope = assertResponseResult(result.structuredContent);
1176
+ return unwrapResponseEnvelope(
1177
+ result,
1178
+ envelope,
1179
+ resourceUri,
1180
+ buildBootstrap,
1181
+ extra
1182
+ );
1183
+ };
1184
+ }
1185
+ async function unwrapResponseEnvelope(adapterResult, envelope, resourceUri, buildBootstrap, extra) {
1186
+ const { data, options, emittedBlocks } = envelope;
1187
+ const textOverride = options?.text;
1188
+ const nudge = options?.nudge;
1189
+ const primaryText = typeof textOverride === "string" ? textOverride : JSON.stringify(data);
1190
+ const content = [
1191
+ ...emittedBlocks ?? [],
1192
+ { type: "text", text: primaryText }
1193
+ ];
1194
+ const existingMeta = typeof adapterResult._meta === "object" && adapterResult._meta !== null ? adapterResult._meta : {};
1195
+ if (nudge) {
1196
+ const structuredContent = buildBootstrap ? {
1197
+ ...await buildBootstrap("nudge", extra),
1198
+ nudge,
1199
+ data
1200
+ } : data;
1201
+ return {
1202
+ ...adapterResult,
1203
+ content,
1204
+ structuredContent,
1205
+ _meta: {
1206
+ ...existingMeta,
1207
+ ...buildNudgeUiMeta({ resourceUri, nudge })
1208
+ }
1209
+ };
1210
+ }
1211
+ return {
1212
+ ...adapterResult,
1213
+ content,
1214
+ structuredContent: data,
1215
+ ...Object.keys(existingMeta).length > 0 ? { _meta: existingMeta } : {}
1216
+ };
1217
+ }
1218
+ function buildNudgeUiMeta(input) {
1219
+ return { ui: { resourceUri: input.resourceUri, nudge: input.nudge } };
1220
+ }
1221
+
1222
+ // src/oauth-discovery.ts
1223
+ var DEFAULT_OAUTH_PATHS = {
1224
+ register: "/oauth/register",
1225
+ authorize: "/oauth/authorize",
1226
+ token: "/oauth/token",
1227
+ revoke: "/oauth/revoke"
1228
+ };
1229
+ function withoutTrailingSlash(value) {
1230
+ return value.replace(/\/$/, "");
1231
+ }
1232
+ function resolveOAuthPaths(paths = {}) {
1233
+ return { ...DEFAULT_OAUTH_PATHS, ...paths };
1234
+ }
1235
+ function getOAuthProtectedResourceResponse(publicBaseUrl) {
1236
+ const resource = withoutTrailingSlash(publicBaseUrl);
1237
+ return {
1238
+ resource,
1239
+ authorization_servers: [resource],
1240
+ scopes_supported: ["openid", "profile", "email"]
1241
+ };
1242
+ }
1243
+ function getOAuthAuthorizationServerResponse({
1244
+ publicBaseUrl,
1245
+ paths
1246
+ }) {
1247
+ const base = withoutTrailingSlash(publicBaseUrl);
1248
+ const p = resolveOAuthPaths(paths);
1249
+ return {
1250
+ issuer: base,
1251
+ authorization_endpoint: `${base}${p.authorize}`,
1252
+ token_endpoint: `${base}${p.token}`,
1253
+ registration_endpoint: `${base}${p.register}`,
1254
+ revocation_endpoint: `${base}${p.revoke}`,
1255
+ token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
1256
+ response_types_supported: ["code"],
1257
+ grant_types_supported: ["authorization_code", "refresh_token"],
1258
+ scopes_supported: ["openid", "profile", "email"],
1259
+ code_challenge_methods_supported: ["S256"]
1260
+ };
1261
+ }
1262
+
1263
+ // src/bearer.ts
1264
+ var McpBearerAuthError = class extends Error {
1265
+ constructor(message) {
1266
+ super(message);
1267
+ this.name = "McpBearerAuthError";
1268
+ }
1269
+ };
1270
+ function base64UrlDecode(input) {
1271
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
1272
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
1273
+ const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
1274
+ return new TextDecoder().decode(bytes);
1275
+ }
1276
+ function extractBearerToken(authorization) {
1277
+ if (!authorization) return null;
1278
+ if (!authorization.startsWith("Bearer ")) return null;
1279
+ return authorization.slice(7).trim() || null;
1280
+ }
1281
+ function decodeJwtPayload(token) {
1282
+ const parts = token.split(".");
1283
+ if (parts.length < 2) {
1284
+ throw new McpBearerAuthError("Invalid JWT format");
1285
+ }
1286
+ try {
1287
+ const payloadText = base64UrlDecode(parts[1]);
1288
+ const payload = JSON.parse(payloadText);
1289
+ return payload;
1290
+ } catch {
1291
+ throw new McpBearerAuthError("Invalid JWT payload");
1292
+ }
1293
+ }
1294
+ function getCustomerRefFromJwtPayload(payload, options = {}) {
1295
+ const claimPriority = options.claimPriority || ["customerRef", "customer_ref", "sub"];
1296
+ for (const claim of claimPriority) {
1297
+ const value = payload[claim];
1298
+ if (typeof value === "string" && value.trim()) {
1299
+ return value.trim();
1300
+ }
1301
+ }
1302
+ throw new McpBearerAuthError(
1303
+ `No customer reference claim found (checked: ${claimPriority.join(", ")})`
1304
+ );
1305
+ }
1306
+ function getCustomerRefFromBearerAuthHeader(authorization, options = {}) {
1307
+ const token = extractBearerToken(authorization);
1308
+ if (!token) {
1309
+ throw new McpBearerAuthError("Missing bearer token");
1310
+ }
1311
+ const payload = decodeJwtPayload(token);
1312
+ return getCustomerRefFromJwtPayload(payload, options);
1313
+ }
1314
+
1315
+ // src/auth-bridge.ts
1316
+ function getClientId(payload, explicitClientId) {
1317
+ if (explicitClientId) return explicitClientId;
1318
+ const payloadClientId = typeof payload.client_id === "string" && payload.client_id || typeof payload.azp === "string" && payload.azp || typeof payload.aud === "string" && payload.aud || null;
1319
+ return payloadClientId || "solvapay-mcp-client";
1320
+ }
1321
+ function getScopes(payload, defaultScopes) {
1322
+ if (Array.isArray(payload.scp)) {
1323
+ return payload.scp.filter((scope) => typeof scope === "string");
1324
+ }
1325
+ if (typeof payload.scope === "string" && payload.scope.trim()) {
1326
+ return payload.scope.split(/\s+/).map((scope) => scope.trim()).filter(Boolean);
1327
+ }
1328
+ return defaultScopes;
1329
+ }
1330
+ function getExpiresAt(payload) {
1331
+ return typeof payload.exp === "number" ? payload.exp : void 0;
1332
+ }
1333
+ function buildAuthInfoFromBearer(authorization, options = {}) {
1334
+ const token = extractBearerToken(authorization);
1335
+ if (!token) return null;
1336
+ const payload = decodeJwtPayload(token);
1337
+ const customerRef = getCustomerRefFromJwtPayload(payload, options);
1338
+ const clientId = getClientId(payload, options.clientId);
1339
+ const scopes = getScopes(payload, options.defaultScopes || []);
1340
+ const expiresAt = getExpiresAt(payload);
1341
+ return {
1342
+ token,
1343
+ clientId,
1344
+ scopes,
1345
+ expiresAt,
1346
+ extra: {
1347
+ customer_ref: customerRef,
1348
+ ...options.includePayload ? { payload } : {}
1349
+ }
1350
+ };
1351
+ }
1352
+ // Annotate the CommonJS export names for ESM import in node:
1353
+ 0 && (module.exports = {
1354
+ DEFAULT_OAUTH_PATHS,
1355
+ MCP_TOOL_NAMES,
1356
+ McpBearerAuthError,
1357
+ NARRATORS,
1358
+ OPEN_TOOL_FOR_VIEW,
1359
+ SOLVAPAY_DEFAULT_CSP,
1360
+ SOLVAPAY_MCP_VIEW_KINDS,
1361
+ SOLVAPAY_OVERVIEW_MARKDOWN,
1362
+ SOLVAPAY_OVERVIEW_MIME_TYPE,
1363
+ SOLVAPAY_OVERVIEW_URI,
1364
+ TOOL_FOR_VIEW,
1365
+ VIEW_FOR_OPEN_TOOL,
1366
+ VIEW_FOR_TOOL,
1367
+ balanceSummary,
1368
+ buildAuthInfoFromBearer,
1369
+ buildPayableHandler,
1370
+ buildPaywallUiMeta,
1371
+ buildSolvaPayDescriptors,
1372
+ buildSolvaPayPrompts,
1373
+ buildSolvaPayRequest,
1374
+ createBuildBootstrapPayload,
1375
+ decodeJwtPayload,
1376
+ defaultGetCustomerRef,
1377
+ enrichPurchase,
1378
+ extractBearerToken,
1379
+ getCustomerRefFromBearerAuthHeader,
1380
+ getCustomerRefFromJwtPayload,
1381
+ getOAuthAuthorizationServerResponse,
1382
+ getOAuthProtectedResourceResponse,
1383
+ mergeCsp,
1384
+ narrateActivatePlan,
1385
+ narrateManageAccount,
1386
+ narrateTopup,
1387
+ narrateUpgrade,
1388
+ narratedToolResult,
1389
+ parseMode,
1390
+ paywallToolResult,
1391
+ previewJson,
1392
+ resolveOAuthPaths,
1393
+ toolErrorResult,
1394
+ toolResult,
1395
+ uiPlaceholder,
1396
+ withoutTrailingSlash
1397
+ });