@solvapay/mcp-core 0.1.0 → 0.2.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.js CHANGED
@@ -17,9 +17,7 @@ var MCP_TOOL_NAMES = {
17
17
  var SOLVAPAY_MCP_VIEW_KINDS = [
18
18
  "checkout",
19
19
  "account",
20
- "topup",
21
- "paywall",
22
- "nudge"
20
+ "topup"
23
21
  ];
24
22
  var TOOL_FOR_VIEW = {
25
23
  checkout: "upgrade",
@@ -365,41 +363,19 @@ function previewJson(value, max = 400) {
365
363
  }
366
364
  }
367
365
 
368
- // src/paywall-meta.ts
369
- function buildPaywallUiMeta(input) {
370
- return {
371
- ui: {
372
- resourceUri: input.resourceUri
373
- }
374
- };
375
- }
376
-
377
366
  // src/paywallToolResult.ts
378
367
  import { PaywallError } from "@solvapay/server";
379
- async function paywallToolResult(errOrGate, ctx) {
368
+ async function paywallToolResult(errOrGate, _ctx = {}) {
380
369
  const paywallContent = errOrGate instanceof PaywallError ? errOrGate.structuredContent : errOrGate;
381
370
  const narrationText = errOrGate instanceof PaywallError ? errOrGate.message : paywallContent.message;
382
- let structuredContent;
383
- if (ctx.buildBootstrap) {
384
- const bootstrap = await ctx.buildBootstrap("paywall", ctx.extra, {
385
- paywall: paywallContent
386
- });
387
- structuredContent = bootstrap;
388
- } else {
389
- structuredContent = paywallContent;
390
- }
391
371
  return {
392
372
  // Deliberately `false`: paywall is a user-actionable gate, not a
393
- // tool failure. Hosts short-circuit on `isError: true` and render
394
- // the error text instead of opening the UI resource advertised by
395
- // `_meta.ui`, which meant the paywall widget never appeared on
396
- // MCPJam / Claude Desktop / ChatGPT Apps. The `structuredContent`
397
- // and `content[0].text` still carry the gate reason so the LLM
398
- // can narrate it, while `_meta.ui` triggers the widget.
373
+ // tool failure. The LLM narrates the recovery from
374
+ // `content[0].text` and the structured gate content on
375
+ // `structuredContent` is available for programmatic consumers.
399
376
  isError: false,
400
377
  content: [{ type: "text", text: narrationText }],
401
- structuredContent,
402
- _meta: buildPaywallUiMeta({ resourceUri: ctx.resourceUri })
378
+ structuredContent: paywallContent
403
379
  };
404
380
  }
405
381
 
@@ -429,6 +405,30 @@ function mergeCsp(overrides) {
429
405
  };
430
406
  }
431
407
 
408
+ // src/hideToolsByAudience.ts
409
+ function applyHideToolsByAudience(server, audiences) {
410
+ if (!audiences || audiences.length === 0) return;
411
+ const hidden = new Set(audiences);
412
+ const inner = server.server;
413
+ if (!inner || typeof inner !== "object" || !(inner._requestHandlers instanceof Map)) {
414
+ return;
415
+ }
416
+ const handlers = inner._requestHandlers;
417
+ const original = handlers.get("tools/list");
418
+ if (!original) return;
419
+ handlers.set("tools/list", async (req, extra) => {
420
+ const res = await original(req, extra);
421
+ const tools = Array.isArray(res?.tools) ? res.tools : [];
422
+ return {
423
+ ...res,
424
+ tools: tools.filter((t) => {
425
+ const audience = t?._meta?.audience;
426
+ return !hidden.has(typeof audience === "string" ? audience : "");
427
+ })
428
+ };
429
+ });
430
+ }
431
+
432
432
  // src/descriptors.ts
433
433
  import {
434
434
  activatePlanCore,
@@ -472,7 +472,7 @@ function createBuildBootstrapPayload(options) {
472
472
  };
473
473
  const buildRequest = (extra) => buildSolvaPayRequest(extra, { getCustomerRef });
474
474
  const productQueryRequest = () => buildSolvaPayRequest(void 0, { query: { productRef }, getCustomerRef: () => null });
475
- return async (view, extra, extras = {}) => {
475
+ return async (view, extra) => {
476
476
  const customerRef = getCustomerRef(extra);
477
477
  const wrapError = (promise) => promise.catch((err) => ({
478
478
  error: err instanceof Error ? err.message : String(err),
@@ -519,7 +519,7 @@ function createBuildBootstrapPayload(options) {
519
519
  balance: okOrNull(balanceResult),
520
520
  usage: okOrNull(usageResult)
521
521
  } : null;
522
- const payload = {
522
+ return {
523
523
  view,
524
524
  productRef,
525
525
  stripePublishableKey,
@@ -529,8 +529,6 @@ function createBuildBootstrapPayload(options) {
529
529
  plans,
530
530
  customer
531
531
  };
532
- if (extras.paywall) payload.paywall = extras.paywall;
533
- return payload;
534
532
  };
535
533
  }
536
534
 
@@ -564,9 +562,13 @@ with clickable URLs for text-only hosts.
564
562
 
565
563
  Each intent tool returns a full \`BootstrapPayload\` (merchant + product + plans
566
564
  + customer snapshot) so the embedded UI never fires per-view read calls. When
567
- a paywalled data tool hits the usage limit, its response carries the same
568
- payload with \`view: "paywall"\`, letting the host open the UI directly on the
569
- paywall screen \u2014 no second tool call required.
565
+ a paywalled data tool hits the usage limit, its response is plain text: the
566
+ \`content[0].text\` narration names the recovery intent tool
567
+ (\`upgrade\` / \`topup\` / \`activate_plan\`) and inlines \`gate.checkoutUrl\` for
568
+ terminal-first hosts. The structured gate rides on \`structuredContent\` for
569
+ programmatic consumers. No widget iframe opens for a gate \u2014 the LLM reads the
570
+ narration and, if the user agrees, calls the named intent tool which mounts
571
+ the checkout / topup / account surface.
570
572
 
571
573
  Auth is handled by the SolvaPay OAuth bridge (see \`createMcpOAuthBridge\`). The
572
574
  bridge injects \`customer_ref\` onto every authenticated request; tools that
@@ -974,7 +976,7 @@ function buildSolvaPayPrompts(options = {}) {
974
976
  }
975
977
 
976
978
  // src/payable-handler.ts
977
- import { isPaywallStructuredContent } from "@solvapay/server";
979
+ import { buildNudgeMessage, isPaywallStructuredContent } from "@solvapay/server";
978
980
 
979
981
  // src/response-context.ts
980
982
  import { PaywallError as PaywallError2 } from "@solvapay/server";
@@ -1079,7 +1081,7 @@ function buildResponseContext(params) {
1079
1081
 
1080
1082
  // src/payable-handler.ts
1081
1083
  function buildPayableHandler(solvaPay, ctx, handler) {
1082
- const { product, resourceUri, buildBootstrap, getCustomerRef } = ctx;
1084
+ const { product, getCustomerRef } = ctx;
1083
1085
  const wrappedBusinessLogic = async (args, handlerContext) => {
1084
1086
  const limits = handlerContext?.limits ?? null;
1085
1087
  const customerRef = handlerContext?.customerRef ?? "";
@@ -1095,60 +1097,44 @@ function buildPayableHandler(solvaPay, ctx, handler) {
1095
1097
  return async (args, extra) => {
1096
1098
  const result = await protectedHandler(args, extra);
1097
1099
  if (isPaywallStructuredContent(result.structuredContent)) {
1098
- const existingMeta = typeof result._meta === "object" && result._meta !== null ? result._meta : {};
1099
- const gateContent = result.structuredContent;
1100
- const structuredContent = buildBootstrap ? await buildBootstrap("paywall", extra, {
1101
- paywall: gateContent
1102
- }) : result.structuredContent;
1103
1100
  return {
1104
1101
  ...result,
1105
1102
  isError: false,
1106
- structuredContent,
1107
- _meta: {
1108
- ...existingMeta,
1109
- ...buildPaywallUiMeta({ resourceUri })
1110
- }
1103
+ structuredContent: result.structuredContent
1111
1104
  };
1112
1105
  }
1113
1106
  if (result.isError) {
1114
1107
  return result;
1115
1108
  }
1116
1109
  const envelope = assertResponseResult(result.structuredContent);
1117
- return unwrapResponseEnvelope(
1118
- result,
1119
- envelope,
1120
- resourceUri,
1121
- buildBootstrap,
1122
- extra
1123
- );
1110
+ return unwrapResponseEnvelope(result, envelope, extra);
1124
1111
  };
1125
1112
  }
1126
- async function unwrapResponseEnvelope(adapterResult, envelope, resourceUri, buildBootstrap, extra) {
1113
+ async function unwrapResponseEnvelope(adapterResult, envelope, _extra) {
1127
1114
  const { data, options, emittedBlocks } = envelope;
1128
1115
  const textOverride = options?.text;
1129
1116
  const nudge = options?.nudge;
1130
- const primaryText = typeof textOverride === "string" ? textOverride : JSON.stringify(data);
1117
+ const baseText = typeof textOverride === "string" ? textOverride : JSON.stringify(data);
1118
+ let primaryText = baseText;
1119
+ if (nudge) {
1120
+ const nudgeText = nudge.message && nudge.message.length > 0 ? nudge.message : buildNudgeMessage(
1121
+ // `buildNudgeMessage` only reads the state kind to pick
1122
+ // copy; for merchant-supplied nudges we don't have a
1123
+ // `LimitResponseWithPlan` in hand here, so we defer to
1124
+ // the nudge's own kind → state mapping. `low-balance` →
1125
+ // topup, everything else → upgrade.
1126
+ nudge.kind === "low-balance" ? { kind: "topup_required" } : { kind: "upgrade_required" },
1127
+ null
1128
+ );
1129
+ primaryText = baseText.length > 0 ? `${baseText}
1130
+
1131
+ ${nudgeText}` : nudgeText;
1132
+ }
1131
1133
  const content = [
1132
1134
  ...emittedBlocks ?? [],
1133
1135
  { type: "text", text: primaryText }
1134
1136
  ];
1135
1137
  const existingMeta = typeof adapterResult._meta === "object" && adapterResult._meta !== null ? adapterResult._meta : {};
1136
- if (nudge) {
1137
- const structuredContent = buildBootstrap ? {
1138
- ...await buildBootstrap("nudge", extra),
1139
- nudge,
1140
- data
1141
- } : data;
1142
- return {
1143
- ...adapterResult,
1144
- content,
1145
- structuredContent,
1146
- _meta: {
1147
- ...existingMeta,
1148
- ...buildNudgeUiMeta({ resourceUri, nudge })
1149
- }
1150
- };
1151
- }
1152
1138
  return {
1153
1139
  ...adapterResult,
1154
1140
  content,
@@ -1156,9 +1142,6 @@ async function unwrapResponseEnvelope(adapterResult, envelope, resourceUri, buil
1156
1142
  ...Object.keys(existingMeta).length > 0 ? { _meta: existingMeta } : {}
1157
1143
  };
1158
1144
  }
1159
- function buildNudgeUiMeta(input) {
1160
- return { ui: { resourceUri: input.resourceUri, nudge: input.nudge } };
1161
- }
1162
1145
 
1163
1146
  // src/oauth-discovery.ts
1164
1147
  var DEFAULT_OAUTH_PATHS = {
@@ -1304,16 +1287,17 @@ export {
1304
1287
  TOOL_FOR_VIEW,
1305
1288
  VIEW_FOR_OPEN_TOOL,
1306
1289
  VIEW_FOR_TOOL,
1290
+ applyHideToolsByAudience,
1307
1291
  balanceSummary,
1308
1292
  buildAuthInfoFromBearer,
1309
1293
  buildPayableHandler,
1310
- buildPaywallUiMeta,
1311
1294
  buildSolvaPayDescriptors,
1312
1295
  buildSolvaPayPrompts,
1313
1296
  buildSolvaPayRequest,
1314
1297
  createBuildBootstrapPayload,
1315
1298
  decodeJwtPayload,
1316
1299
  defaultGetCustomerRef,
1300
+ deriveIcons,
1317
1301
  enrichPurchase,
1318
1302
  extractBearerToken,
1319
1303
  getCustomerRefFromBearerAuthHeader,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/mcp-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-neutral MCP contracts for the SolvaPay SDK (tool names, descriptors, payable handler, paywall meta, CSP, bootstrap payload, OAuth discovery JSON builders, bearer/JWT helpers).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -34,14 +34,14 @@
34
34
  },
35
35
  "peerDependencies": {
36
36
  "zod": "^3.25.0 || ^4.0.0",
37
- "@solvapay/server": "1.0.8-preview.10"
37
+ "@solvapay/server": "^1.0.8"
38
38
  },
39
39
  "devDependencies": {
40
40
  "tsup": "^8.5.1",
41
41
  "typescript": "^5.9.3",
42
42
  "vitest": "^4.1.2",
43
43
  "zod": "^4.3.6",
44
- "@solvapay/server": "1.0.8-preview.10",
44
+ "@solvapay/server": "1.0.8",
45
45
  "@solvapay/test-utils": "^0.0.0"
46
46
  },
47
47
  "scripts": {