@solvapay/server 1.0.0-preview.20 → 1.0.0-preview.21

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 CHANGED
@@ -4254,14 +4254,21 @@ var init_esm = __esm({
4254
4254
  // src/index.ts
4255
4255
  var index_exports = {};
4256
4256
  __export(index_exports, {
4257
+ McpBearerAuthError: () => McpBearerAuthError,
4257
4258
  PaywallError: () => PaywallError,
4259
+ VIRTUAL_TOOL_DEFINITIONS: () => VIRTUAL_TOOL_DEFINITIONS,
4258
4260
  cancelPurchaseCore: () => cancelPurchaseCore,
4259
4261
  createCheckoutSessionCore: () => createCheckoutSessionCore,
4260
4262
  createCustomerSessionCore: () => createCustomerSessionCore,
4261
4263
  createPaymentIntentCore: () => createPaymentIntentCore,
4262
4264
  createSolvaPay: () => createSolvaPay,
4263
4265
  createSolvaPayClient: () => createSolvaPayClient,
4266
+ createVirtualTools: () => createVirtualTools,
4267
+ decodeJwtPayload: () => decodeJwtPayload,
4268
+ extractBearerToken: () => extractBearerToken,
4264
4269
  getAuthenticatedUserCore: () => getAuthenticatedUserCore,
4270
+ getCustomerRefFromBearerAuthHeader: () => getCustomerRefFromBearerAuthHeader,
4271
+ getCustomerRefFromJwtPayload: () => getCustomerRefFromJwtPayload,
4265
4272
  handleRouteError: () => handleRouteError,
4266
4273
  isErrorResult: () => isErrorResult,
4267
4274
  listPlansCore: () => listPlansCore,
@@ -4309,10 +4316,12 @@ function createSolvaPayClient(opts) {
4309
4316
  // POST: /v1/sdk/usages
4310
4317
  async trackUsage(params) {
4311
4318
  const url = `${base}/v1/sdk/usages`;
4319
+ const { customerRef, ...rest } = params;
4320
+ const body = { ...rest, customerId: customerRef };
4312
4321
  const res = await fetch(url, {
4313
4322
  method: "POST",
4314
4323
  headers,
4315
- body: JSON.stringify(params)
4324
+ body: JSON.stringify(body)
4316
4325
  });
4317
4326
  if (!res.ok) {
4318
4327
  const error = await res.text();
@@ -4416,6 +4425,21 @@ function createSolvaPayClient(opts) {
4416
4425
  const result = await res.json();
4417
4426
  return result;
4418
4427
  },
4428
+ // POST: /v1/sdk/products/mcp/bootstrap
4429
+ async bootstrapMcpProduct(params) {
4430
+ const url = `${base}/v1/sdk/products/mcp/bootstrap`;
4431
+ const res = await fetch(url, {
4432
+ method: "POST",
4433
+ headers,
4434
+ body: JSON.stringify(params)
4435
+ });
4436
+ if (!res.ok) {
4437
+ const error = await res.text();
4438
+ log(`\u274C API Error: ${res.status} - ${error}`);
4439
+ throw new import_core.SolvaPayError(`Bootstrap MCP product failed (${res.status}): ${error}`);
4440
+ }
4441
+ return await res.json();
4442
+ },
4419
4443
  // DELETE: /v1/sdk/products/{productRef}
4420
4444
  async deleteProduct(productRef) {
4421
4445
  const url = `${base}/v1/sdk/products/${productRef}`;
@@ -4429,6 +4453,21 @@ function createSolvaPayClient(opts) {
4429
4453
  throw new import_core.SolvaPayError(`Delete product failed (${res.status}): ${error}`);
4430
4454
  }
4431
4455
  },
4456
+ // POST: /v1/sdk/products/{productRef}/clone
4457
+ async cloneProduct(productRef, overrides) {
4458
+ const url = `${base}/v1/sdk/products/${productRef}/clone`;
4459
+ const res = await fetch(url, {
4460
+ method: "POST",
4461
+ headers,
4462
+ body: JSON.stringify(overrides || {})
4463
+ });
4464
+ if (!res.ok) {
4465
+ const error = await res.text();
4466
+ log(`\u274C API Error: ${res.status} - ${error}`);
4467
+ throw new import_core.SolvaPayError(`Clone product failed (${res.status}): ${error}`);
4468
+ }
4469
+ return await res.json();
4470
+ },
4432
4471
  // GET: /v1/sdk/products/{productRef}/plans
4433
4472
  async listPlans(productRef) {
4434
4473
  const url = `${base}/v1/sdk/products/${productRef}/plans`;
@@ -4471,6 +4510,21 @@ function createSolvaPayClient(opts) {
4471
4510
  const result = await res.json();
4472
4511
  return result;
4473
4512
  },
4513
+ // PUT: /v1/sdk/products/{productRef}/plans/{planRef}
4514
+ async updatePlan(productRef, planRef, params) {
4515
+ const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
4516
+ const res = await fetch(url, {
4517
+ method: "PUT",
4518
+ headers,
4519
+ body: JSON.stringify(params)
4520
+ });
4521
+ if (!res.ok) {
4522
+ const error = await res.text();
4523
+ log(`\u274C API Error: ${res.status} - ${error}`);
4524
+ throw new import_core.SolvaPayError(`Update plan failed (${res.status}): ${error}`);
4525
+ }
4526
+ return await res.json();
4527
+ },
4474
4528
  // DELETE: /v1/sdk/products/{productRef}/plans/{planRef}
4475
4529
  async deletePlan(productRef, planRef) {
4476
4530
  const url = `${base}/v1/sdk/products/${productRef}/plans/${planRef}`;
@@ -4583,6 +4637,21 @@ function createSolvaPayClient(opts) {
4583
4637
  }
4584
4638
  return result;
4585
4639
  },
4640
+ // POST: /v1/sdk/user-info
4641
+ async getUserInfo(params) {
4642
+ const url = `${base}/v1/sdk/user-info`;
4643
+ const res = await fetch(url, {
4644
+ method: "POST",
4645
+ headers,
4646
+ body: JSON.stringify(params)
4647
+ });
4648
+ if (!res.ok) {
4649
+ const error = await res.text();
4650
+ log(`\u274C API Error: ${res.status} - ${error}`);
4651
+ throw new import_core.SolvaPayError(`Get user info failed (${res.status}): ${error}`);
4652
+ }
4653
+ return await res.json();
4654
+ },
4586
4655
  // POST: /v1/sdk/checkout-sessions
4587
4656
  async createCheckoutSession(params) {
4588
4657
  const url = `${base}/v1/sdk/checkout-sessions`;
@@ -4781,11 +4850,13 @@ var SolvaPayPaywall = class {
4781
4850
  constructor(apiClient, options = {}) {
4782
4851
  this.apiClient = apiClient;
4783
4852
  this.debug = options.debug ?? process.env.SOLVAPAY_DEBUG === "true";
4853
+ this.limitsCacheTTL = options.limitsCacheTTL ?? 1e4;
4784
4854
  }
4785
4855
  customerCreationAttempts = /* @__PURE__ */ new Set();
4786
4856
  customerRefMapping = /* @__PURE__ */ new Map();
4787
- // input ref -> backend ref
4788
4857
  debug;
4858
+ limitsCache = /* @__PURE__ */ new Map();
4859
+ limitsCacheTTL;
4789
4860
  log(...args) {
4790
4861
  if (this.debug) {
4791
4862
  console.log(...args);
@@ -4805,7 +4876,9 @@ var SolvaPayPaywall = class {
4805
4876
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4806
4877
  async protect(handler, metadata = {}, getCustomerRef) {
4807
4878
  const product = this.resolveProduct(metadata);
4808
- const toolName = handler.name || "anonymous";
4879
+ const configuredPlanRef = metadata.plan?.trim();
4880
+ const usagePlanRef = configuredPlanRef || "unspecified";
4881
+ const usageType = metadata.usageType || "requests";
4809
4882
  return async (args) => {
4810
4883
  const startTime = Date.now();
4811
4884
  const requestId = this.generateRequestId();
@@ -4816,19 +4889,64 @@ var SolvaPayPaywall = class {
4816
4889
  } else {
4817
4890
  backendCustomerRef = await this.ensureCustomer(inputCustomerRef, inputCustomerRef);
4818
4891
  }
4892
+ let resolvedMeterName;
4819
4893
  try {
4820
- const planRef = metadata.plan || toolName;
4821
- const limitsCheck = await this.apiClient.checkLimits({
4822
- customerRef: backendCustomerRef,
4823
- productRef: product
4824
- });
4825
- if (!limitsCheck.withinLimits) {
4894
+ const limitsCacheKey = `${backendCustomerRef}:${product}:${configuredPlanRef || ""}:${usageType}`;
4895
+ const cachedLimits = this.limitsCache.get(limitsCacheKey);
4896
+ const now = Date.now();
4897
+ let withinLimits;
4898
+ let remaining;
4899
+ let checkoutUrl;
4900
+ const hasFreshCachedLimits = cachedLimits && now - cachedLimits.timestamp < this.limitsCacheTTL;
4901
+ if (hasFreshCachedLimits) {
4902
+ checkoutUrl = cachedLimits.checkoutUrl;
4903
+ resolvedMeterName = cachedLimits.meterName;
4904
+ if (cachedLimits.remaining > 0) {
4905
+ cachedLimits.remaining--;
4906
+ if (cachedLimits.remaining <= 0) {
4907
+ this.limitsCache.delete(limitsCacheKey);
4908
+ }
4909
+ withinLimits = true;
4910
+ remaining = cachedLimits.remaining;
4911
+ } else {
4912
+ withinLimits = false;
4913
+ remaining = 0;
4914
+ this.limitsCache.delete(limitsCacheKey);
4915
+ }
4916
+ } else {
4917
+ if (cachedLimits) {
4918
+ this.limitsCache.delete(limitsCacheKey);
4919
+ }
4920
+ const limitsCheck = await this.apiClient.checkLimits({
4921
+ customerRef: backendCustomerRef,
4922
+ productRef: product,
4923
+ ...configuredPlanRef ? { planRef: configuredPlanRef } : {},
4924
+ meterName: usageType
4925
+ });
4926
+ withinLimits = limitsCheck.withinLimits;
4927
+ remaining = limitsCheck.remaining;
4928
+ checkoutUrl = limitsCheck.checkoutUrl;
4929
+ resolvedMeterName = limitsCheck.meterName;
4930
+ const consumedAllowance = withinLimits && remaining > 0;
4931
+ if (consumedAllowance) {
4932
+ remaining = Math.max(0, remaining - 1);
4933
+ }
4934
+ if (consumedAllowance) {
4935
+ this.limitsCache.set(limitsCacheKey, {
4936
+ remaining,
4937
+ checkoutUrl,
4938
+ meterName: resolvedMeterName,
4939
+ timestamp: now
4940
+ });
4941
+ }
4942
+ }
4943
+ if (!withinLimits) {
4826
4944
  const latencyMs2 = Date.now() - startTime;
4827
- await this.trackUsage(
4945
+ this.trackUsage(
4828
4946
  backendCustomerRef,
4829
4947
  product,
4830
- planRef,
4831
- toolName,
4948
+ usagePlanRef,
4949
+ resolvedMeterName || usageType,
4832
4950
  "paywall",
4833
4951
  requestId,
4834
4952
  latencyMs2
@@ -4836,17 +4954,17 @@ var SolvaPayPaywall = class {
4836
4954
  throw new PaywallError("Payment required", {
4837
4955
  kind: "payment_required",
4838
4956
  product,
4839
- checkoutUrl: limitsCheck.checkoutUrl || "",
4840
- message: `Plan purchase required. Remaining: ${limitsCheck.remaining}`
4957
+ checkoutUrl: checkoutUrl || "",
4958
+ message: `Purchase required. Remaining: ${remaining}`
4841
4959
  });
4842
4960
  }
4843
4961
  const result = await handler(args);
4844
4962
  const latencyMs = Date.now() - startTime;
4845
- await this.trackUsage(
4963
+ this.trackUsage(
4846
4964
  backendCustomerRef,
4847
4965
  product,
4848
- planRef,
4849
- toolName,
4966
+ usagePlanRef,
4967
+ resolvedMeterName || usageType,
4850
4968
  "success",
4851
4969
  requestId,
4852
4970
  latencyMs
@@ -4859,18 +4977,18 @@ var SolvaPayPaywall = class {
4859
4977
  } else {
4860
4978
  this.log(`\u274C Error in paywall:`, error);
4861
4979
  }
4862
- const latencyMs = Date.now() - startTime;
4863
- const outcome = error instanceof PaywallError ? "paywall" : "fail";
4864
- const planRef = metadata.plan || toolName;
4865
- await this.trackUsage(
4866
- backendCustomerRef,
4867
- product,
4868
- planRef,
4869
- toolName,
4870
- outcome,
4871
- requestId,
4872
- latencyMs
4873
- );
4980
+ if (!(error instanceof PaywallError)) {
4981
+ const latencyMs = Date.now() - startTime;
4982
+ this.trackUsage(
4983
+ backendCustomerRef,
4984
+ product,
4985
+ usagePlanRef,
4986
+ resolvedMeterName || usageType,
4987
+ "fail",
4988
+ requestId,
4989
+ latencyMs
4990
+ );
4991
+ }
4874
4992
  throw error;
4875
4993
  }
4876
4994
  };
@@ -5019,24 +5137,23 @@ var SolvaPayPaywall = class {
5019
5137
  }
5020
5138
  return backendRef;
5021
5139
  }
5022
- async trackUsage(customerRef, productRef, planRef, toolName, outcome, requestId, actionDuration) {
5140
+ async trackUsage(customerRef, _productRef, _planRef, action, outcome, requestId, actionDuration) {
5023
5141
  await withRetry(
5024
5142
  () => this.apiClient.trackUsage({
5025
5143
  customerRef,
5026
- productRef,
5027
- planRef,
5144
+ actionType: "api_call",
5145
+ units: 1,
5028
5146
  outcome,
5029
- action: toolName,
5030
- requestId,
5031
- actionDuration,
5147
+ productReference: _productRef,
5148
+ duration: actionDuration,
5149
+ metadata: { action: action || "api_requests", requestId },
5032
5150
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5033
5151
  }),
5034
5152
  {
5035
5153
  maxRetries: 2,
5036
5154
  initialDelay: 500,
5037
5155
  shouldRetry: (error) => error.message.includes("Customer not found"),
5038
- // TODO: review if this is needed and what to check for
5039
- onRetry: (error, attempt) => {
5156
+ onRetry: (_error, attempt) => {
5040
5157
  console.warn(`\u26A0\uFE0F Customer not found (attempt ${attempt + 1}/3), retrying in 500ms...`);
5041
5158
  }
5042
5159
  }
@@ -5077,13 +5194,19 @@ var AdapterUtils = class {
5077
5194
  }
5078
5195
  };
5079
5196
  async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
5197
+ const backendRefCache = /* @__PURE__ */ new Map();
5198
+ const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
5199
+ const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
5080
5200
  return async (context) => {
5081
5201
  try {
5082
5202
  const args = await adapter.extractArgs(context);
5083
5203
  const customerRef = await adapter.getCustomerRef(context);
5084
- args.auth = { customer_ref: customerRef };
5085
- const getCustomerRef = (args2) => args2.auth?.customer_ref || "anonymous";
5086
- const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
5204
+ let backendRef = backendRefCache.get(customerRef);
5205
+ if (!backendRef) {
5206
+ backendRef = await paywall.ensureCustomer(customerRef, customerRef);
5207
+ backendRefCache.set(customerRef, backendRef);
5208
+ }
5209
+ args.auth = { customer_ref: backendRef };
5087
5210
  const result = await protectedHandler(args);
5088
5211
  return adapter.formatResponse(result, context);
5089
5212
  } catch (error) {
@@ -5332,6 +5455,141 @@ var McpAdapter = class {
5332
5455
 
5333
5456
  // src/factory.ts
5334
5457
  var import_core2 = require("@solvapay/core");
5458
+
5459
+ // src/virtual-tools.ts
5460
+ var TOOL_GET_USER_INFO = {
5461
+ name: "get_user_info",
5462
+ description: "Get information about the current user and their purchase status for this MCP server. Returns user profile (reference, name, email) and active purchase details including product name, type, dates, and usage limit if applicable.",
5463
+ inputSchema: {
5464
+ type: "object",
5465
+ properties: {},
5466
+ required: []
5467
+ }
5468
+ };
5469
+ var TOOL_UPGRADE = {
5470
+ name: "upgrade",
5471
+ description: "Get available pricing options and checkout URLs for upgrading. Returns a list of available pricing options with their details (price, features) and checkout URLs. Users can click on a checkout URL to purchase. If a specific planRef is provided, returns only the checkout URL for that pricing option.",
5472
+ inputSchema: {
5473
+ type: "object",
5474
+ properties: {
5475
+ planRef: {
5476
+ type: "string",
5477
+ description: 'Optional pricing reference (e.g., "pln_abc123") to get a checkout URL for a specific option. If not provided, returns all available pricing options with their checkout URLs.'
5478
+ }
5479
+ },
5480
+ required: []
5481
+ }
5482
+ };
5483
+ var TOOL_MANAGE_ACCOUNT = {
5484
+ name: "manage_account",
5485
+ description: "Get a URL to the customer portal where users can view and manage their account. The portal shows current account status, billing history, and allows subscription changes. Returns a secure, time-limited URL that the user can click to access their account management page.",
5486
+ inputSchema: {
5487
+ type: "object",
5488
+ properties: {},
5489
+ required: []
5490
+ }
5491
+ };
5492
+ var VIRTUAL_TOOL_DEFINITIONS = [TOOL_GET_USER_INFO, TOOL_UPGRADE, TOOL_MANAGE_ACCOUNT];
5493
+ function mcpTextResult(text) {
5494
+ return { content: [{ type: "text", text }] };
5495
+ }
5496
+ function mcpErrorResult(message2) {
5497
+ return { content: [{ type: "text", text: JSON.stringify({ error: message2 }) }], isError: true };
5498
+ }
5499
+ function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
5500
+ return async (args) => {
5501
+ const customerRef = getCustomerRef(args);
5502
+ try {
5503
+ if (!apiClient.getUserInfo) {
5504
+ return mcpErrorResult("getUserInfo is not available on this API client");
5505
+ }
5506
+ const userInfo = await apiClient.getUserInfo({ customerRef, productRef });
5507
+ return mcpTextResult(JSON.stringify(userInfo, null, 2));
5508
+ } catch (error) {
5509
+ return mcpErrorResult(
5510
+ `Failed to retrieve user information: ${error instanceof Error ? error.message : "Unknown error"}`
5511
+ );
5512
+ }
5513
+ };
5514
+ }
5515
+ function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
5516
+ return async (args) => {
5517
+ const customerRef = getCustomerRef(args);
5518
+ const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
5519
+ try {
5520
+ const result = await apiClient.createCheckoutSession({
5521
+ customerReference: customerRef,
5522
+ productRef,
5523
+ ...planRef && { planRef }
5524
+ });
5525
+ const checkoutUrl = result.checkoutUrl;
5526
+ if (planRef) {
5527
+ const responseText2 = `## Upgrade
5528
+
5529
+ **[Click here to upgrade \u2192](${checkoutUrl})**
5530
+
5531
+ After completing the checkout, your purchase will be activated immediately.`;
5532
+ return mcpTextResult(responseText2);
5533
+ }
5534
+ const responseText = `## Upgrade Your Subscription
5535
+
5536
+ **[Click here to view pricing options and upgrade \u2192](${checkoutUrl})**
5537
+
5538
+ You'll be able to compare options and select the one that's right for you.`;
5539
+ return mcpTextResult(responseText);
5540
+ } catch (error) {
5541
+ return mcpErrorResult(
5542
+ `Failed to create checkout session: ${error instanceof Error ? error.message : "Unknown error"}`
5543
+ );
5544
+ }
5545
+ };
5546
+ }
5547
+ function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
5548
+ return async (args) => {
5549
+ const customerRef = getCustomerRef(args);
5550
+ try {
5551
+ const session = await apiClient.createCustomerSession({ customerRef, productRef });
5552
+ const portalUrl = session.customerUrl;
5553
+ const responseText = `## Manage Your Account
5554
+
5555
+ Access your account management portal to:
5556
+ - View your current account status
5557
+ - See billing history and invoices
5558
+ - Update payment methods
5559
+ - Cancel or modify your subscription
5560
+
5561
+ **[Open Account Portal \u2192](${portalUrl})**
5562
+
5563
+ This link is secure and will expire after a short period.`;
5564
+ return mcpTextResult(responseText);
5565
+ } catch (error) {
5566
+ return mcpErrorResult(
5567
+ `Failed to create customer portal session: ${error instanceof Error ? error.message : "Unknown error"}`
5568
+ );
5569
+ }
5570
+ };
5571
+ }
5572
+ function createVirtualTools(apiClient, options) {
5573
+ const { product, getCustomerRef, exclude = [] } = options;
5574
+ const excludeSet = new Set(exclude);
5575
+ const allTools = [
5576
+ {
5577
+ ...TOOL_GET_USER_INFO,
5578
+ handler: createGetUserInfoHandler(apiClient, product, getCustomerRef)
5579
+ },
5580
+ {
5581
+ ...TOOL_UPGRADE,
5582
+ handler: createUpgradeHandler(apiClient, product, getCustomerRef)
5583
+ },
5584
+ {
5585
+ ...TOOL_MANAGE_ACCOUNT,
5586
+ handler: createManageAccountHandler(apiClient, product, getCustomerRef)
5587
+ }
5588
+ ];
5589
+ return allTools.filter((t) => !excludeSet.has(t.name));
5590
+ }
5591
+
5592
+ // src/factory.ts
5335
5593
  function createSolvaPay(config) {
5336
5594
  let resolvedConfig;
5337
5595
  if (!config) {
@@ -5348,7 +5606,8 @@ function createSolvaPay(config) {
5348
5606
  apiBaseUrl: resolvedConfig.apiBaseUrl
5349
5607
  });
5350
5608
  const paywall = new SolvaPayPaywall(apiClient, {
5351
- debug: process.env.SOLVAPAY_DEBUG !== "false"
5609
+ debug: process.env.SOLVAPAY_DEBUG !== "false",
5610
+ limitsCacheTTL: resolvedConfig.limitsCacheTTL
5352
5611
  });
5353
5612
  return {
5354
5613
  // Direct access to API client for advanced operations
@@ -5394,39 +5653,67 @@ function createSolvaPay(config) {
5394
5653
  createCustomerSession(params) {
5395
5654
  return apiClient.createCustomerSession(params);
5396
5655
  },
5656
+ bootstrapMcpProduct(params) {
5657
+ if (!apiClient.bootstrapMcpProduct) {
5658
+ throw new import_core2.SolvaPayError("bootstrapMcpProduct is not available on this API client");
5659
+ }
5660
+ return apiClient.bootstrapMcpProduct(params);
5661
+ },
5662
+ getVirtualTools(options) {
5663
+ return createVirtualTools(apiClient, options);
5664
+ },
5397
5665
  // Payable API for framework-specific handlers
5398
5666
  payable(options = {}) {
5399
5667
  const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
5400
- const plan = options.planRef || options.plan || product;
5401
- const metadata = { product, plan };
5668
+ const plan = options.planRef || options.plan;
5669
+ const usageType = options.usageType || "requests";
5670
+ const metadata = { product, plan, usageType };
5402
5671
  return {
5403
5672
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5404
5673
  http(businessLogic, adapterOptions) {
5405
- const adapter = new HttpAdapter(adapterOptions);
5674
+ const adapter = new HttpAdapter({
5675
+ ...adapterOptions,
5676
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
5677
+ });
5678
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
5406
5679
  return async (req, reply) => {
5407
- const handler = await createAdapterHandler(adapter, paywall, metadata, businessLogic);
5680
+ const handler = await handlerPromise;
5408
5681
  return handler([req, reply]);
5409
5682
  };
5410
5683
  },
5411
5684
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5412
5685
  next(businessLogic, adapterOptions) {
5413
- const adapter = new NextAdapter(adapterOptions);
5686
+ const adapter = new NextAdapter({
5687
+ ...adapterOptions,
5688
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
5689
+ });
5690
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
5414
5691
  return async (request, context) => {
5415
- const handler = await createAdapterHandler(adapter, paywall, metadata, businessLogic);
5692
+ const handler = await handlerPromise;
5416
5693
  return handler([request, context]);
5417
5694
  };
5418
5695
  },
5419
5696
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5420
5697
  mcp(businessLogic, adapterOptions) {
5421
- const adapter = new McpAdapter(adapterOptions);
5698
+ const adapter = new McpAdapter({
5699
+ ...adapterOptions,
5700
+ getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
5701
+ });
5702
+ const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
5422
5703
  return async (args) => {
5423
- const handler = await createAdapterHandler(adapter, paywall, metadata, businessLogic);
5704
+ const handler = await handlerPromise;
5424
5705
  return handler(args);
5425
5706
  };
5426
5707
  },
5427
5708
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5428
5709
  async function(businessLogic) {
5429
- const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
5710
+ const getCustomerRef = (args) => {
5711
+ const configuredRef = options.getCustomerRef?.(args);
5712
+ if (typeof configuredRef === "string") {
5713
+ return configuredRef;
5714
+ }
5715
+ return args.auth?.customer_ref || "anonymous";
5716
+ };
5430
5717
  return paywall.protect(businessLogic, metadata, getCustomerRef);
5431
5718
  }
5432
5719
  };
@@ -5434,6 +5721,57 @@ function createSolvaPay(config) {
5434
5721
  };
5435
5722
  }
5436
5723
 
5724
+ // src/mcp-auth.ts
5725
+ var McpBearerAuthError = class extends Error {
5726
+ constructor(message2) {
5727
+ super(message2);
5728
+ this.name = "McpBearerAuthError";
5729
+ }
5730
+ };
5731
+ function base64UrlDecode(input) {
5732
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
5733
+ const padded = normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "=");
5734
+ return Buffer.from(padded, "base64").toString("utf8");
5735
+ }
5736
+ function extractBearerToken(authorization) {
5737
+ if (!authorization) return null;
5738
+ if (!authorization.startsWith("Bearer ")) return null;
5739
+ return authorization.slice(7).trim() || null;
5740
+ }
5741
+ function decodeJwtPayload(token) {
5742
+ const parts = token.split(".");
5743
+ if (parts.length < 2) {
5744
+ throw new McpBearerAuthError("Invalid JWT format");
5745
+ }
5746
+ try {
5747
+ const payloadText = base64UrlDecode(parts[1]);
5748
+ const payload = JSON.parse(payloadText);
5749
+ return payload;
5750
+ } catch {
5751
+ throw new McpBearerAuthError("Invalid JWT payload");
5752
+ }
5753
+ }
5754
+ function getCustomerRefFromJwtPayload(payload, options = {}) {
5755
+ const claimPriority = options.claimPriority || ["customerRef", "customer_ref", "sub"];
5756
+ for (const claim of claimPriority) {
5757
+ const value = payload[claim];
5758
+ if (typeof value === "string" && value.trim()) {
5759
+ return value.trim();
5760
+ }
5761
+ }
5762
+ throw new McpBearerAuthError(
5763
+ `No customer reference claim found (checked: ${claimPriority.join(", ")})`
5764
+ );
5765
+ }
5766
+ function getCustomerRefFromBearerAuthHeader(authorization, options = {}) {
5767
+ const token = extractBearerToken(authorization);
5768
+ if (!token) {
5769
+ throw new McpBearerAuthError("Missing bearer token");
5770
+ }
5771
+ const payload = decodeJwtPayload(token);
5772
+ return getCustomerRefFromJwtPayload(payload, options);
5773
+ }
5774
+
5437
5775
  // src/helpers/error.ts
5438
5776
  var import_core3 = require("@solvapay/core");
5439
5777
  function isErrorResult(result) {
@@ -5759,14 +6097,21 @@ function verifyWebhook({
5759
6097
  }
5760
6098
  // Annotate the CommonJS export names for ESM import in node:
5761
6099
  0 && (module.exports = {
6100
+ McpBearerAuthError,
5762
6101
  PaywallError,
6102
+ VIRTUAL_TOOL_DEFINITIONS,
5763
6103
  cancelPurchaseCore,
5764
6104
  createCheckoutSessionCore,
5765
6105
  createCustomerSessionCore,
5766
6106
  createPaymentIntentCore,
5767
6107
  createSolvaPay,
5768
6108
  createSolvaPayClient,
6109
+ createVirtualTools,
6110
+ decodeJwtPayload,
6111
+ extractBearerToken,
5769
6112
  getAuthenticatedUserCore,
6113
+ getCustomerRefFromBearerAuthHeader,
6114
+ getCustomerRefFromJwtPayload,
5770
6115
  handleRouteError,
5771
6116
  isErrorResult,
5772
6117
  listPlansCore,