@solvapay/server 1.0.2 → 1.0.4

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
@@ -4314,9 +4314,11 @@ __export(index_exports, {
4314
4314
  McpBearerAuthError: () => McpBearerAuthError,
4315
4315
  PaywallError: () => PaywallError,
4316
4316
  VIRTUAL_TOOL_DEFINITIONS: () => VIRTUAL_TOOL_DEFINITIONS,
4317
+ buildAuthInfoFromBearer: () => buildAuthInfoFromBearer,
4317
4318
  cancelPurchaseCore: () => cancelPurchaseCore,
4318
4319
  createCheckoutSessionCore: () => createCheckoutSessionCore,
4319
4320
  createCustomerSessionCore: () => createCustomerSessionCore,
4321
+ createMcpOAuthBridge: () => createMcpOAuthBridge,
4320
4322
  createPaymentIntentCore: () => createPaymentIntentCore,
4321
4323
  createSolvaPay: () => createSolvaPay,
4322
4324
  createSolvaPayClient: () => createSolvaPayClient,
@@ -4326,10 +4328,14 @@ __export(index_exports, {
4326
4328
  getAuthenticatedUserCore: () => getAuthenticatedUserCore,
4327
4329
  getCustomerRefFromBearerAuthHeader: () => getCustomerRefFromBearerAuthHeader,
4328
4330
  getCustomerRefFromJwtPayload: () => getCustomerRefFromJwtPayload,
4331
+ getOAuthAuthorizationServerResponse: () => getOAuthAuthorizationServerResponse,
4332
+ getOAuthProtectedResourceResponse: () => getOAuthProtectedResourceResponse,
4329
4333
  handleRouteError: () => handleRouteError,
4330
4334
  isErrorResult: () => isErrorResult,
4335
+ jsonSchemaToZodRawShape: () => jsonSchemaToZodRawShape,
4331
4336
  listPlansCore: () => listPlansCore,
4332
4337
  processPaymentIntentCore: () => processPaymentIntentCore,
4338
+ registerVirtualToolsMcpImpl: () => registerVirtualToolsMcpImpl,
4333
4339
  syncCustomerCore: () => syncCustomerCore,
4334
4340
  verifyWebhook: () => verifyWebhook,
4335
4341
  withRetry: () => withRetry
@@ -4497,6 +4503,21 @@ function createSolvaPayClient(opts) {
4497
4503
  }
4498
4504
  return await res.json();
4499
4505
  },
4506
+ // PUT: /v1/sdk/products/{productRef}/mcp/plans
4507
+ async configureMcpPlans(productRef, params) {
4508
+ const url = `${base}/v1/sdk/products/${productRef}/mcp/plans`;
4509
+ const res = await fetch(url, {
4510
+ method: "PUT",
4511
+ headers,
4512
+ body: JSON.stringify(params)
4513
+ });
4514
+ if (!res.ok) {
4515
+ const error = await res.text();
4516
+ log(`\u274C API Error: ${res.status} - ${error}`);
4517
+ throw new import_core.SolvaPayError(`Configure MCP plans failed (${res.status}): ${error}`);
4518
+ }
4519
+ return await res.json();
4520
+ },
4500
4521
  // DELETE: /v1/sdk/products/{productRef}
4501
4522
  async deleteProduct(productRef) {
4502
4523
  const url = `${base}/v1/sdk/products/${productRef}`;
@@ -5254,10 +5275,10 @@ async function createAdapterHandler(adapter, paywall, metadata, businessLogic) {
5254
5275
  const backendRefCache = /* @__PURE__ */ new Map();
5255
5276
  const getCustomerRef = (args) => args.auth?.customer_ref || "anonymous";
5256
5277
  const protectedHandler = await paywall.protect(businessLogic, metadata, getCustomerRef);
5257
- return async (context) => {
5278
+ return async (context, extra) => {
5258
5279
  try {
5259
5280
  const args = await adapter.extractArgs(context);
5260
- const customerRef = await adapter.getCustomerRef(context);
5281
+ const customerRef = await adapter.getCustomerRef(context, extra);
5261
5282
  let backendRef = backendRefCache.get(customerRef);
5262
5283
  if (!backendRef) {
5263
5284
  backendRef = await paywall.ensureCustomer(customerRef, customerRef);
@@ -5448,18 +5469,18 @@ var McpAdapter = class {
5448
5469
  extractArgs(args) {
5449
5470
  return args;
5450
5471
  }
5451
- async getCustomerRef(args) {
5472
+ async getCustomerRef(args, extra) {
5452
5473
  if (this.options.getCustomerRef) {
5453
- const ref = await this.options.getCustomerRef(args);
5474
+ const ref = await this.options.getCustomerRef(args, extra);
5454
5475
  return AdapterUtils.ensureCustomerRef(ref);
5455
5476
  }
5456
- const auth = args?.auth;
5457
- const customerRef = auth?.customer_ref || "anonymous";
5477
+ const customerRefFromExtra = extra?.authInfo?.extra?.customer_ref;
5478
+ const customerRef = typeof customerRefFromExtra === "string" && customerRefFromExtra.trim() ? customerRefFromExtra.trim() : "anonymous";
5458
5479
  return AdapterUtils.ensureCustomerRef(customerRef);
5459
5480
  }
5460
5481
  formatResponse(result, _context) {
5461
5482
  const transformed = this.options.transformResponse ? this.options.transformResponse(result) : result;
5462
- return {
5483
+ const response = {
5463
5484
  content: [
5464
5485
  {
5465
5486
  type: "text",
@@ -5467,6 +5488,10 @@ var McpAdapter = class {
5467
5488
  }
5468
5489
  ]
5469
5490
  };
5491
+ if (transformed && typeof transformed === "object" && !Array.isArray(transformed)) {
5492
+ response.structuredContent = transformed;
5493
+ }
5494
+ return response;
5470
5495
  }
5471
5496
  formatError(error, _context) {
5472
5497
  if (error instanceof PaywallError) {
@@ -5554,8 +5579,8 @@ function mcpErrorResult(message2) {
5554
5579
  return { content: [{ type: "text", text: JSON.stringify({ error: message2 }) }], isError: true };
5555
5580
  }
5556
5581
  function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
5557
- return async (args) => {
5558
- const customerRef = getCustomerRef(args);
5582
+ return async (args, extra) => {
5583
+ const customerRef = getCustomerRef(args, extra);
5559
5584
  try {
5560
5585
  if (!apiClient.getUserInfo) {
5561
5586
  return mcpErrorResult("getUserInfo is not available on this API client");
@@ -5570,12 +5595,12 @@ function createGetUserInfoHandler(apiClient, productRef, getCustomerRef) {
5570
5595
  };
5571
5596
  }
5572
5597
  function createUpgradeHandler(apiClient, productRef, getCustomerRef) {
5573
- return async (args) => {
5574
- const customerRef = getCustomerRef(args);
5598
+ return async (args, extra) => {
5599
+ const customerRef = getCustomerRef(args, extra);
5575
5600
  const planRef = typeof args.planRef === "string" ? args.planRef : void 0;
5576
5601
  try {
5577
5602
  const result = await apiClient.createCheckoutSession({
5578
- customerReference: customerRef,
5603
+ customerRef,
5579
5604
  productRef,
5580
5605
  ...planRef && { planRef }
5581
5606
  });
@@ -5602,8 +5627,8 @@ You'll be able to compare options and select the one that's right for you.`;
5602
5627
  };
5603
5628
  }
5604
5629
  function createManageAccountHandler(apiClient, productRef, getCustomerRef) {
5605
- return async (args) => {
5606
- const customerRef = getCustomerRef(args);
5630
+ return async (args, extra) => {
5631
+ const customerRef = getCustomerRef(args, extra);
5607
5632
  try {
5608
5633
  const session = await apiClient.createCustomerSession({ customerRef, productRef });
5609
5634
  const portalUrl = session.customerUrl;
@@ -5646,6 +5671,98 @@ function createVirtualTools(apiClient, options) {
5646
5671
  return allTools.filter((t) => !excludeSet.has(t.name));
5647
5672
  }
5648
5673
 
5674
+ // src/register-virtual-tools-mcp.ts
5675
+ var import_node_module = require("module");
5676
+ function getNodeRequire() {
5677
+ try {
5678
+ return (0, eval)("require");
5679
+ } catch {
5680
+ return (0, import_node_module.createRequire)(`${process.cwd()}/package.json`);
5681
+ }
5682
+ }
5683
+ var nodeRequire = getNodeRequire();
5684
+ function getZod() {
5685
+ try {
5686
+ const zodModule = nodeRequire("zod");
5687
+ return zodModule.z ?? zodModule;
5688
+ } catch {
5689
+ throw new Error(
5690
+ "zod is required to use registerVirtualToolsMcp(). Install it as a dependency in your MCP server project."
5691
+ );
5692
+ }
5693
+ }
5694
+ function isJsonSchemaObject(value) {
5695
+ return Boolean(value && typeof value === "object");
5696
+ }
5697
+ function toZodSchema(property) {
5698
+ const z = getZod();
5699
+ if (Array.isArray(property.enum) && property.enum.length > 0) {
5700
+ if (property.enum.every((value) => typeof value === "string")) {
5701
+ const [first, ...rest] = property.enum;
5702
+ return z.enum([first, ...rest]);
5703
+ }
5704
+ if (property.enum.length === 1) {
5705
+ return z.literal(property.enum[0]);
5706
+ }
5707
+ return z.union(property.enum.map((value) => z.literal(value)));
5708
+ }
5709
+ switch (property.type) {
5710
+ case "string":
5711
+ return z.string();
5712
+ case "number":
5713
+ return z.number();
5714
+ case "integer":
5715
+ return z.number().int();
5716
+ case "boolean":
5717
+ return z.boolean();
5718
+ case "array":
5719
+ return z.array(
5720
+ isJsonSchemaObject(property.items) ? toZodSchema(property.items) : z.any()
5721
+ );
5722
+ case "object":
5723
+ return z.object(jsonSchemaToZodRawShape(property.properties || {}));
5724
+ default:
5725
+ return z.any();
5726
+ }
5727
+ }
5728
+ function jsonSchemaToZodRawShape(properties, required = []) {
5729
+ const requiredSet = new Set(required);
5730
+ return Object.fromEntries(
5731
+ Object.entries(properties).map(([name, property]) => {
5732
+ const schema = toZodSchema(property);
5733
+ const finalSchema = requiredSet.has(name) ? schema : schema.optional();
5734
+ return [name, finalSchema];
5735
+ })
5736
+ );
5737
+ }
5738
+ function defaultGetCustomerRef(_args, extra) {
5739
+ const fromExtra = extra?.authInfo?.extra?.customer_ref;
5740
+ return typeof fromExtra === "string" && fromExtra.trim() ? fromExtra.trim() : "anonymous";
5741
+ }
5742
+ function registerVirtualToolsMcpImpl(server, apiClient, options) {
5743
+ const { filter, mapDefinition, wrapHandler, getCustomerRef, ...virtualToolsOptions } = options;
5744
+ const virtualTools = createVirtualTools(apiClient, {
5745
+ ...virtualToolsOptions,
5746
+ getCustomerRef: getCustomerRef || defaultGetCustomerRef
5747
+ });
5748
+ for (const toolDefinition of virtualTools) {
5749
+ if (filter && !filter(toolDefinition)) continue;
5750
+ const mappedDefinition = mapDefinition ? mapDefinition(toolDefinition) : toolDefinition;
5751
+ const wrappedHandler = wrapHandler ? wrapHandler(mappedDefinition.handler, mappedDefinition) : mappedDefinition.handler;
5752
+ server.registerTool(
5753
+ mappedDefinition.name,
5754
+ {
5755
+ description: mappedDefinition.description,
5756
+ inputSchema: jsonSchemaToZodRawShape(
5757
+ mappedDefinition.inputSchema.properties,
5758
+ mappedDefinition.inputSchema.required || []
5759
+ )
5760
+ },
5761
+ wrappedHandler
5762
+ );
5763
+ }
5764
+ }
5765
+
5649
5766
  // src/factory.ts
5650
5767
  function createSolvaPay(config) {
5651
5768
  let resolvedConfig;
@@ -5702,7 +5819,7 @@ function createSolvaPay(config) {
5702
5819
  },
5703
5820
  createCheckoutSession(params) {
5704
5821
  return apiClient.createCheckoutSession({
5705
- customerReference: params.customerRef,
5822
+ customerRef: params.customerRef,
5706
5823
  productRef: params.productRef,
5707
5824
  planRef: params.planRef
5708
5825
  });
@@ -5716,9 +5833,18 @@ function createSolvaPay(config) {
5716
5833
  }
5717
5834
  return apiClient.bootstrapMcpProduct(params);
5718
5835
  },
5836
+ configureMcpPlans(productRef, params) {
5837
+ if (!apiClient.configureMcpPlans) {
5838
+ throw new import_core2.SolvaPayError("configureMcpPlans is not available on this API client");
5839
+ }
5840
+ return apiClient.configureMcpPlans(productRef, params);
5841
+ },
5719
5842
  getVirtualTools(options) {
5720
5843
  return createVirtualTools(apiClient, options);
5721
5844
  },
5845
+ async registerVirtualToolsMcp(server, options) {
5846
+ await registerVirtualToolsMcpImpl(server, apiClient, options);
5847
+ },
5722
5848
  // Payable API for framework-specific handlers
5723
5849
  payable(options = {}) {
5724
5850
  const product = options.productRef || options.product || process.env.SOLVAPAY_PRODUCT || "default-product";
@@ -5757,9 +5883,9 @@ function createSolvaPay(config) {
5757
5883
  getCustomerRef: adapterOptions?.getCustomerRef || options.getCustomerRef
5758
5884
  });
5759
5885
  const handlerPromise = createAdapterHandler(adapter, paywall, metadata, businessLogic);
5760
- return async (args) => {
5886
+ return async (args, extra) => {
5761
5887
  const handler = await handlerPromise;
5762
- return handler(args);
5888
+ return handler(args, extra);
5763
5889
  };
5764
5890
  },
5765
5891
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -5829,6 +5955,167 @@ function getCustomerRefFromBearerAuthHeader(authorization, options = {}) {
5829
5955
  return getCustomerRefFromJwtPayload(payload, options);
5830
5956
  }
5831
5957
 
5958
+ // src/mcp/auth-bridge.ts
5959
+ function getClientId(payload, explicitClientId) {
5960
+ if (explicitClientId) return explicitClientId;
5961
+ const payloadClientId = typeof payload.client_id === "string" && payload.client_id || typeof payload.azp === "string" && payload.azp || typeof payload.aud === "string" && payload.aud || null;
5962
+ return payloadClientId || "solvapay-mcp-client";
5963
+ }
5964
+ function getScopes(payload, defaultScopes) {
5965
+ if (Array.isArray(payload.scp)) {
5966
+ return payload.scp.filter((scope) => typeof scope === "string");
5967
+ }
5968
+ if (typeof payload.scope === "string" && payload.scope.trim()) {
5969
+ return payload.scope.split(/\s+/).map((scope) => scope.trim()).filter(Boolean);
5970
+ }
5971
+ return defaultScopes;
5972
+ }
5973
+ function getExpiresAt(payload) {
5974
+ return typeof payload.exp === "number" ? payload.exp : void 0;
5975
+ }
5976
+ function buildAuthInfoFromBearer(authorization, options = {}) {
5977
+ const token = extractBearerToken(authorization);
5978
+ if (!token) return null;
5979
+ const payload = decodeJwtPayload(token);
5980
+ const customerRef = getCustomerRefFromJwtPayload(payload, options);
5981
+ const clientId = getClientId(payload, options.clientId);
5982
+ const scopes = getScopes(payload, options.defaultScopes || []);
5983
+ const expiresAt = getExpiresAt(payload);
5984
+ return {
5985
+ token,
5986
+ clientId,
5987
+ scopes,
5988
+ expiresAt,
5989
+ extra: {
5990
+ customer_ref: customerRef,
5991
+ ...options.includePayload ? { payload } : {}
5992
+ }
5993
+ };
5994
+ }
5995
+
5996
+ // src/mcp/oauth-bridge.ts
5997
+ function withoutTrailingSlash(value) {
5998
+ return value.replace(/\/$/, "");
5999
+ }
6000
+ function getRequestAuthHeader(req) {
6001
+ const header = req.headers?.authorization;
6002
+ if (typeof header === "string") return header;
6003
+ if (Array.isArray(header)) return header[0] || null;
6004
+ return null;
6005
+ }
6006
+ function getRequestJsonRpcId(body) {
6007
+ if (body && typeof body === "object" && "id" in body) {
6008
+ const id = body.id;
6009
+ return id ?? null;
6010
+ }
6011
+ return null;
6012
+ }
6013
+ function makeUnauthorizedJsonRpc(id) {
6014
+ return {
6015
+ jsonrpc: "2.0",
6016
+ id,
6017
+ error: {
6018
+ code: -32001,
6019
+ message: "Unauthorized"
6020
+ }
6021
+ };
6022
+ }
6023
+ function setMcpChallengeHeader(res, publicBaseUrl, protectedResourcePath) {
6024
+ res.setHeader(
6025
+ "WWW-Authenticate",
6026
+ `Bearer resource_metadata="${withoutTrailingSlash(publicBaseUrl)}${protectedResourcePath}"`
6027
+ );
6028
+ }
6029
+ function getOAuthProtectedResourceResponse(publicBaseUrl) {
6030
+ const resource = withoutTrailingSlash(publicBaseUrl);
6031
+ return {
6032
+ resource,
6033
+ authorization_servers: [resource],
6034
+ scopes_supported: ["openid", "profile", "email"]
6035
+ };
6036
+ }
6037
+ function getOAuthAuthorizationServerResponse({
6038
+ apiBaseUrl,
6039
+ productRef
6040
+ }) {
6041
+ const normalizedApiBaseUrl = withoutTrailingSlash(apiBaseUrl);
6042
+ const registrationEndpoint = `${normalizedApiBaseUrl}/v1/customer/auth/register?product_ref=${encodeURIComponent(productRef)}`;
6043
+ return {
6044
+ issuer: normalizedApiBaseUrl,
6045
+ authorization_endpoint: `${normalizedApiBaseUrl}/v1/customer/auth/authorize`,
6046
+ token_endpoint: `${normalizedApiBaseUrl}/v1/customer/auth/token`,
6047
+ registration_endpoint: registrationEndpoint,
6048
+ token_endpoint_auth_methods_supported: ["client_secret_basic", "client_secret_post"],
6049
+ response_types_supported: ["code"],
6050
+ grant_types_supported: ["authorization_code", "refresh_token"],
6051
+ scopes_supported: ["openid", "profile", "email"],
6052
+ code_challenge_methods_supported: ["S256"]
6053
+ };
6054
+ }
6055
+ function createMcpOAuthBridge(options) {
6056
+ const {
6057
+ publicBaseUrl,
6058
+ apiBaseUrl,
6059
+ productRef,
6060
+ mcpPath = "/mcp",
6061
+ requireAuth = true,
6062
+ authInfo,
6063
+ protectedResourcePath = "/.well-known/oauth-protected-resource",
6064
+ authorizationServerPath = "/.well-known/oauth-authorization-server"
6065
+ } = options;
6066
+ const protectedResourceMiddleware = (req, res, next) => {
6067
+ if (req.method !== "GET" || req.path !== protectedResourcePath) {
6068
+ next();
6069
+ return;
6070
+ }
6071
+ res.json(getOAuthProtectedResourceResponse(publicBaseUrl));
6072
+ };
6073
+ const authorizationServerMiddleware = (req, res, next) => {
6074
+ if (req.method !== "GET" || req.path !== authorizationServerPath) {
6075
+ next();
6076
+ return;
6077
+ }
6078
+ if (!productRef) {
6079
+ res.status(500).json({ error: "SOLVAPAY_PRODUCT_REF missing" });
6080
+ return;
6081
+ }
6082
+ res.json(
6083
+ getOAuthAuthorizationServerResponse({
6084
+ apiBaseUrl,
6085
+ productRef
6086
+ })
6087
+ );
6088
+ };
6089
+ const mcpAuthMiddleware = (req, res, next) => {
6090
+ if (req.path !== mcpPath) {
6091
+ next();
6092
+ return;
6093
+ }
6094
+ const authHeader = getRequestAuthHeader(req);
6095
+ const id = getRequestJsonRpcId(req.body);
6096
+ if (!authHeader && !requireAuth) {
6097
+ next();
6098
+ return;
6099
+ }
6100
+ try {
6101
+ const auth = buildAuthInfoFromBearer(authHeader, authInfo);
6102
+ if (!auth) {
6103
+ throw new McpBearerAuthError("Missing bearer token");
6104
+ }
6105
+ req.auth = auth;
6106
+ next();
6107
+ } catch {
6108
+ setMcpChallengeHeader(res, publicBaseUrl, protectedResourcePath);
6109
+ if (req.method === "POST") {
6110
+ res.status(401).json(makeUnauthorizedJsonRpc(id));
6111
+ return;
6112
+ }
6113
+ res.status(401).json({ error: "Unauthorized" });
6114
+ }
6115
+ };
6116
+ return [protectedResourceMiddleware, authorizationServerMiddleware, mcpAuthMiddleware];
6117
+ }
6118
+
5832
6119
  // src/helpers/error.ts
5833
6120
  var import_core3 = require("@solvapay/core");
5834
6121
  function isErrorResult(result) {
@@ -6183,9 +6470,11 @@ function verifyWebhook({
6183
6470
  McpBearerAuthError,
6184
6471
  PaywallError,
6185
6472
  VIRTUAL_TOOL_DEFINITIONS,
6473
+ buildAuthInfoFromBearer,
6186
6474
  cancelPurchaseCore,
6187
6475
  createCheckoutSessionCore,
6188
6476
  createCustomerSessionCore,
6477
+ createMcpOAuthBridge,
6189
6478
  createPaymentIntentCore,
6190
6479
  createSolvaPay,
6191
6480
  createSolvaPayClient,
@@ -6195,10 +6484,14 @@ function verifyWebhook({
6195
6484
  getAuthenticatedUserCore,
6196
6485
  getCustomerRefFromBearerAuthHeader,
6197
6486
  getCustomerRefFromJwtPayload,
6487
+ getOAuthAuthorizationServerResponse,
6488
+ getOAuthProtectedResourceResponse,
6198
6489
  handleRouteError,
6199
6490
  isErrorResult,
6491
+ jsonSchemaToZodRawShape,
6200
6492
  listPlansCore,
6201
6493
  processPaymentIntentCore,
6494
+ registerVirtualToolsMcpImpl,
6202
6495
  syncCustomerCore,
6203
6496
  verifyWebhook,
6204
6497
  withRetry