@agentcash/discovery 1.1.3 → 1.3.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 CHANGED
@@ -44,15 +44,18 @@ __export(index_exports, {
44
44
  getL3: () => getL3,
45
45
  getL3ForOpenAPI: () => getL3ForOpenAPI,
46
46
  getL3ForProbe: () => getL3ForProbe,
47
+ getMppWellKnown: () => getMppWellKnown,
47
48
  getOpenAPI: () => getOpenAPI,
48
49
  getProbe: () => getProbe,
49
50
  getWarningsFor402Body: () => getWarningsFor402Body,
50
51
  getWarningsForL2: () => getWarningsForL2,
51
52
  getWarningsForL3: () => getWarningsForL3,
52
53
  getWarningsForL4: () => getWarningsForL4,
54
+ getWarningsForMppHeader: () => getWarningsForMppHeader,
53
55
  getWarningsForOpenAPI: () => getWarningsForOpenAPI,
54
56
  getWarningsForWellKnown: () => getWarningsForWellKnown,
55
57
  getWellKnown: () => getWellKnown,
58
+ getX402WellKnown: () => getX402WellKnown,
56
59
  validatePaymentRequiredDetailed: () => validatePaymentRequiredDetailed
57
60
  });
58
61
  module.exports = __toCommonJS(index_exports);
@@ -13895,7 +13898,8 @@ var WellKnownParsedSchema = external_exports.object({
13895
13898
  routes: external_exports.array(
13896
13899
  external_exports.object({
13897
13900
  path: external_exports.string(),
13898
- method: external_exports.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"])
13901
+ method: external_exports.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"]),
13902
+ price: external_exports.string().optional()
13899
13903
  })
13900
13904
  ),
13901
13905
  instructions: external_exports.string().optional()
@@ -13988,9 +13992,9 @@ function toAbsoluteUrl(origin, value) {
13988
13992
 
13989
13993
  // src/core/source/fetch.ts
13990
13994
  var import_neverthrow = require("neverthrow");
13991
- function toFetchError(err) {
13992
- const cause = err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError") ? "timeout" : "network";
13993
- return { cause, message: String(err) };
13995
+ function toFetchError(err2) {
13996
+ const cause = err2 instanceof DOMException && (err2.name === "TimeoutError" || err2.name === "AbortError") ? "timeout" : "network";
13997
+ return { cause, message: String(err2) };
13994
13998
  }
13995
13999
  function fetchSafe(url2, init) {
13996
14000
  return import_neverthrow.ResultAsync.fromPromise(fetch(url2, init), toFetchError);
@@ -14056,6 +14060,9 @@ function getOpenAPI(origin, headers, signal, specificationOverrideUrl) {
14056
14060
  }
14057
14061
 
14058
14062
  // src/core/source/wellknown/index.ts
14063
+ var import_neverthrow5 = require("neverthrow");
14064
+
14065
+ // src/core/source/wellknown/x402.ts
14059
14066
  var import_neverthrow3 = require("neverthrow");
14060
14067
  function toWellKnownParsed(origin, doc) {
14061
14068
  const routes = doc.resources.flatMap((entry) => {
@@ -14082,12 +14089,17 @@ async function parseBody2(response, origin, url2) {
14082
14089
  if (!doc.success) return null;
14083
14090
  const parsed = WellKnownParsedSchema.safeParse(toWellKnownParsed(origin, doc.data));
14084
14091
  if (!parsed.success) return null;
14085
- return { raw: payload, ...parsed.data, fetchedUrl: url2 };
14092
+ return {
14093
+ raw: payload,
14094
+ ...parsed.data,
14095
+ protocol: "x402",
14096
+ fetchedUrl: url2
14097
+ };
14086
14098
  } catch {
14087
14099
  return null;
14088
14100
  }
14089
14101
  }
14090
- function getWellKnown(origin, headers, signal) {
14102
+ function getX402WellKnown(origin, headers, signal) {
14091
14103
  const url2 = `${origin}/.well-known/x402`;
14092
14104
  return fetchSafe(url2, {
14093
14105
  method: "GET",
@@ -14099,6 +14111,127 @@ function getWellKnown(origin, headers, signal) {
14099
14111
  });
14100
14112
  }
14101
14113
 
14114
+ // src/core/source/wellknown/mpp.ts
14115
+ var import_neverthrow4 = require("neverthrow");
14116
+ var MppEndpointSchema = external_exports.object({
14117
+ method: external_exports.string(),
14118
+ path: external_exports.string(),
14119
+ description: external_exports.string().optional(),
14120
+ payment: external_exports.object({
14121
+ intent: external_exports.string().optional(),
14122
+ method: external_exports.string().optional(),
14123
+ amount: external_exports.string().optional(),
14124
+ currency: external_exports.string().optional()
14125
+ }).optional()
14126
+ });
14127
+ var MppWellKnownDocSchema = external_exports.object({
14128
+ version: external_exports.number().optional(),
14129
+ name: external_exports.string().optional(),
14130
+ description: external_exports.string().optional(),
14131
+ categories: external_exports.array(external_exports.string()).optional(),
14132
+ methods: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
14133
+ endpoints: external_exports.array(MppEndpointSchema).default([]),
14134
+ docs: external_exports.object({
14135
+ homepage: external_exports.string().optional(),
14136
+ apiReference: external_exports.string().optional()
14137
+ }).optional()
14138
+ });
14139
+ var MPP_DECIMALS = 6;
14140
+ function formatMppAmount(raw) {
14141
+ if (!raw) return void 0;
14142
+ const n = Number(raw);
14143
+ if (!Number.isFinite(n)) return void 0;
14144
+ return `$${(n / 10 ** MPP_DECIMALS).toFixed(MPP_DECIMALS)}`;
14145
+ }
14146
+ function toWellKnownParsed2(doc) {
14147
+ const routes = doc.endpoints.flatMap((entry) => {
14148
+ const method = parseMethod(entry.method);
14149
+ if (!method) return [];
14150
+ const path = normalizePath(entry.path);
14151
+ if (!path) return [];
14152
+ const price = formatMppAmount(entry.payment?.amount);
14153
+ return [{ path, method, ...price ? { price } : {} }];
14154
+ });
14155
+ return {
14156
+ routes,
14157
+ ...doc.description ? { instructions: doc.description } : {}
14158
+ };
14159
+ }
14160
+ async function parseBody3(response, url2) {
14161
+ try {
14162
+ const payload = await response.json();
14163
+ const doc = MppWellKnownDocSchema.safeParse(payload);
14164
+ if (!doc.success) return null;
14165
+ const parsed = WellKnownParsedSchema.safeParse(toWellKnownParsed2(doc.data));
14166
+ if (!parsed.success) return null;
14167
+ return {
14168
+ raw: payload,
14169
+ ...parsed.data,
14170
+ ...doc.data.name ? { title: doc.data.name } : {},
14171
+ ...doc.data.description ? { description: doc.data.description } : {},
14172
+ protocol: "mpp",
14173
+ fetchedUrl: url2
14174
+ };
14175
+ } catch {
14176
+ return null;
14177
+ }
14178
+ }
14179
+ function getMppWellKnown(origin, headers, signal) {
14180
+ const url2 = `${origin}/.well-known/mpp`;
14181
+ return fetchSafe(url2, {
14182
+ method: "GET",
14183
+ headers: { Accept: "application/json", ...headers },
14184
+ signal
14185
+ }).andThen((response) => {
14186
+ if (!response.ok) return (0, import_neverthrow4.okAsync)(null);
14187
+ return import_neverthrow4.ResultAsync.fromSafePromise(parseBody3(response, url2));
14188
+ });
14189
+ }
14190
+
14191
+ // src/core/source/wellknown/index.ts
14192
+ function mergeError(a, b) {
14193
+ return {
14194
+ cause: a.cause === "network" || b.cause === "network" ? "network" : "timeout",
14195
+ message: `x402: ${a.message} | mpp: ${b.message}`
14196
+ };
14197
+ }
14198
+ function merge2(x402, mpp) {
14199
+ const seen = /* @__PURE__ */ new Set();
14200
+ const routes = [...x402.routes, ...mpp.routes].filter((r) => {
14201
+ const key = `${r.method} ${r.path}`;
14202
+ if (seen.has(key)) return false;
14203
+ seen.add(key);
14204
+ return true;
14205
+ });
14206
+ return {
14207
+ raw: { ...mpp.raw, ...x402.raw },
14208
+ routes,
14209
+ protocol: "x402+mpp",
14210
+ // Prefer x402 instructions; fall back to mpp
14211
+ ...x402.instructions || mpp.instructions ? { instructions: x402.instructions ?? mpp.instructions } : {},
14212
+ fetchedUrl: x402.fetchedUrl
14213
+ };
14214
+ }
14215
+ function getWellKnown(origin, headers, signal) {
14216
+ return new import_neverthrow5.ResultAsync(
14217
+ Promise.all([
14218
+ getX402WellKnown(origin, headers, signal),
14219
+ getMppWellKnown(origin, headers, signal)
14220
+ ]).then(([x402Result, mppResult]) => {
14221
+ const x402 = x402Result.isOk() ? x402Result.value : null;
14222
+ const mpp = mppResult.isOk() ? mppResult.value : null;
14223
+ if (x402 && mpp) return (0, import_neverthrow5.ok)(merge2(x402, mpp));
14224
+ if (x402) return (0, import_neverthrow5.ok)(x402);
14225
+ if (mpp) return (0, import_neverthrow5.ok)(mpp);
14226
+ if (x402Result.isErr() && mppResult.isErr())
14227
+ return (0, import_neverthrow5.err)(mergeError(x402Result.error, mppResult.error));
14228
+ if (x402Result.isErr()) return (0, import_neverthrow5.err)(x402Result.error);
14229
+ if (mppResult.isErr()) return (0, import_neverthrow5.err)(mppResult.error);
14230
+ return (0, import_neverthrow5.ok)(null);
14231
+ })
14232
+ );
14233
+ }
14234
+
14102
14235
  // src/core/layers/l2.ts
14103
14236
  function formatPrice(pricing) {
14104
14237
  if (pricing.pricingMode === "fixed") return `$${pricing.price}`;
@@ -14124,15 +14257,27 @@ function checkL2ForOpenAPI(openApi) {
14124
14257
  source: "openapi"
14125
14258
  };
14126
14259
  }
14260
+ var WELL_KNOWN_PROTOCOLS = {
14261
+ x402: ["x402"],
14262
+ mpp: ["mpp"],
14263
+ "x402+mpp": ["x402", "mpp"]
14264
+ };
14127
14265
  function checkL2ForWellknown(wellKnown) {
14266
+ const protocols = WELL_KNOWN_PROTOCOLS[wellKnown.protocol];
14128
14267
  const routes = wellKnown.routes.map((route) => ({
14129
14268
  path: route.path,
14130
14269
  method: route.method,
14131
14270
  summary: `${route.method} ${route.path}`,
14132
14271
  authMode: "paid",
14133
- protocols: ["x402"]
14272
+ protocols,
14273
+ ...route.price ? { price: route.price } : {}
14134
14274
  }));
14135
- return { routes, source: "well-known/x402" };
14275
+ return {
14276
+ ...wellKnown.title ? { title: wellKnown.title } : {},
14277
+ ...wellKnown.description ? { description: wellKnown.description } : {},
14278
+ routes,
14279
+ source: `well-known/${wellKnown.protocol}`
14280
+ };
14136
14281
  }
14137
14282
 
14138
14283
  // src/core/layers/l4.ts
@@ -14144,7 +14289,7 @@ function checkL4ForOpenAPI(openApi) {
14144
14289
  }
14145
14290
  function checkL4ForWellknown(wellKnown) {
14146
14291
  if (wellKnown.instructions) {
14147
- return { guidance: wellKnown.instructions, source: "well-known/x402" };
14292
+ return { guidance: wellKnown.instructions, source: `well-known/${wellKnown.protocol}` };
14148
14293
  }
14149
14294
  return null;
14150
14295
  }
@@ -14217,14 +14362,20 @@ async function discoverOriginSchema(options) {
14217
14362
  const base = {
14218
14363
  found: true,
14219
14364
  origin,
14220
- source: "well-known/x402",
14365
+ source: l2.source,
14366
+ ...l2.title ? {
14367
+ info: {
14368
+ title: l2.title,
14369
+ ...l2.description ? { description: l2.description } : {}
14370
+ }
14371
+ } : {},
14221
14372
  endpoints: l2.routes
14222
14373
  };
14223
14374
  return withGuidance(base, l4, guidanceMode);
14224
14375
  }
14225
14376
 
14226
14377
  // src/core/source/probe/index.ts
14227
- var import_neverthrow4 = require("neverthrow");
14378
+ var import_neverthrow6 = require("neverthrow");
14228
14379
 
14229
14380
  // src/core/protocols/x402/v1/schema.ts
14230
14381
  function extractSchemas(accepts) {
@@ -14674,8 +14825,8 @@ function probeMethod(url2, method, path, headers, signal, inputBody) {
14674
14825
  ...hasBody ? { body: JSON.stringify(inputBody) } : {},
14675
14826
  signal
14676
14827
  }).andThen((response) => {
14677
- if (!isUsableStatus(response.status)) return import_neverthrow4.ResultAsync.fromSafePromise(Promise.resolve(null));
14678
- return import_neverthrow4.ResultAsync.fromSafePromise(
14828
+ if (!isUsableStatus(response.status)) return import_neverthrow6.ResultAsync.fromSafePromise(Promise.resolve(null));
14829
+ return import_neverthrow6.ResultAsync.fromSafePromise(
14679
14830
  (async () => {
14680
14831
  let authHint = response.status === 402 ? "paid" : "unprotected";
14681
14832
  let paymentRequiredBody;
@@ -14706,7 +14857,7 @@ function probeMethod(url2, method, path, headers, signal, inputBody) {
14706
14857
  }
14707
14858
  function getProbe(url2, headers, signal, inputBody) {
14708
14859
  const path = normalizePath(new URL(url2).pathname || "/");
14709
- return import_neverthrow4.ResultAsync.fromSafePromise(
14860
+ return import_neverthrow6.ResultAsync.fromSafePromise(
14710
14861
  Promise.all(
14711
14862
  [...HTTP_METHODS].map(
14712
14863
  (method) => probeMethod(url2, method, path, headers, signal, inputBody).match(
@@ -14719,6 +14870,20 @@ function getProbe(url2, headers, signal, inputBody) {
14719
14870
  }
14720
14871
 
14721
14872
  // src/core/protocols/mpp/index.ts
14873
+ var import_neverthrow7 = require("neverthrow");
14874
+ function parseBase64Json(encoded) {
14875
+ return import_neverthrow7.Result.fromThrowable(
14876
+ () => {
14877
+ const decoded = typeof Buffer !== "undefined" ? Buffer.from(encoded, "base64").toString("utf8") : atob(encoded);
14878
+ const parsed = JSON.parse(decoded);
14879
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
14880
+ throw new Error("not an object");
14881
+ }
14882
+ return parsed;
14883
+ },
14884
+ (e) => e
14885
+ )();
14886
+ }
14722
14887
  var TEMPO_DEFAULT_CHAIN_ID = 4217;
14723
14888
  function parseAuthParams(segment) {
14724
14889
  const params = {};
@@ -14741,14 +14906,9 @@ function extractPaymentOptions4(wwwAuthenticate) {
14741
14906
  const description = params["description"];
14742
14907
  const requestStr = params["request"];
14743
14908
  if (!paymentMethod || !intent || !realm || !requestStr) continue;
14744
- let request;
14745
- try {
14746
- const parsed = JSON.parse(requestStr);
14747
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
14748
- request = parsed;
14749
- } catch {
14750
- continue;
14751
- }
14909
+ const requestResult = parseBase64Json(requestStr);
14910
+ if (requestResult.isErr()) continue;
14911
+ const request = requestResult.value;
14752
14912
  const asset = typeof request["currency"] === "string" ? request["currency"] : void 0;
14753
14913
  const amountRaw = request["amount"];
14754
14914
  const amount = typeof amountRaw === "string" ? amountRaw : typeof amountRaw === "number" ? String(amountRaw) : void 0;
@@ -14930,7 +15090,8 @@ function getL3ForProbe(probe, path, method) {
14930
15090
  ...inputSchema ? { inputSchema } : {},
14931
15091
  ...outputSchema ? { outputSchema } : {},
14932
15092
  ...paymentOptions.length ? { paymentOptions } : {},
14933
- ...probeResult.paymentRequiredBody !== void 0 ? { paymentRequiredBody: probeResult.paymentRequiredBody } : {}
15093
+ ...probeResult.paymentRequiredBody !== void 0 ? { paymentRequiredBody: probeResult.paymentRequiredBody } : {},
15094
+ ...probeResult.wwwAuthenticate ? { wwwAuthenticate: probeResult.wwwAuthenticate } : {}
14934
15095
  };
14935
15096
  }
14936
15097
  async function attachProbePayload(url2, advisories) {
@@ -15111,9 +15272,23 @@ var AUDIT_CODES = {
15111
15272
  L3_INPUT_SCHEMA_MISSING: "L3_INPUT_SCHEMA_MISSING",
15112
15273
  L3_AUTH_MODE_MISSING: "L3_AUTH_MODE_MISSING",
15113
15274
  L3_PROTOCOLS_MISSING_ON_PAID: "L3_PROTOCOLS_MISSING_ON_PAID",
15275
+ L3_PAYMENT_OPTIONS_MISSING_ON_PAID: "L3_PAYMENT_OPTIONS_MISSING_ON_PAID",
15114
15276
  // ─── L4 guidance checks ──────────────────────────────────────────────────────
15115
15277
  L4_GUIDANCE_MISSING: "L4_GUIDANCE_MISSING",
15116
- L4_GUIDANCE_TOO_LONG: "L4_GUIDANCE_TOO_LONG"
15278
+ L4_GUIDANCE_TOO_LONG: "L4_GUIDANCE_TOO_LONG",
15279
+ // ─── MPP WWW-Authenticate header checks ──────────────────────────────────────
15280
+ MPP_HEADER_MISSING: "MPP_HEADER_MISSING",
15281
+ MPP_NO_PAYMENT_CHALLENGES: "MPP_NO_PAYMENT_CHALLENGES",
15282
+ MPP_CHALLENGE_ID_MISSING: "MPP_CHALLENGE_ID_MISSING",
15283
+ MPP_CHALLENGE_METHOD_MISSING: "MPP_CHALLENGE_METHOD_MISSING",
15284
+ MPP_CHALLENGE_INTENT_MISSING: "MPP_CHALLENGE_INTENT_MISSING",
15285
+ MPP_CHALLENGE_REALM_MISSING: "MPP_CHALLENGE_REALM_MISSING",
15286
+ MPP_CHALLENGE_EXPIRES_MISSING: "MPP_CHALLENGE_EXPIRES_MISSING",
15287
+ MPP_CHALLENGE_REQUEST_MISSING: "MPP_CHALLENGE_REQUEST_MISSING",
15288
+ MPP_CHALLENGE_REQUEST_INVALID: "MPP_CHALLENGE_REQUEST_INVALID",
15289
+ MPP_CHALLENGE_ASSET_MISSING: "MPP_CHALLENGE_ASSET_MISSING",
15290
+ MPP_CHALLENGE_AMOUNT_MISSING: "MPP_CHALLENGE_AMOUNT_MISSING",
15291
+ MPP_CHALLENGE_RECIPIENT_MISSING: "MPP_CHALLENGE_RECIPIENT_MISSING"
15117
15292
  };
15118
15293
 
15119
15294
  // src/core/protocols/x402/v1/coinbase-schema.ts
@@ -15202,6 +15377,157 @@ function validateWithCoinbaseSchema2(body) {
15202
15377
  });
15203
15378
  }
15204
15379
 
15380
+ // src/audit/warnings/mpp.ts
15381
+ function parseAuthParams2(segment) {
15382
+ const params = {};
15383
+ const re = /(\w+)=(?:"([^"]*)"|'([^']*)')/g;
15384
+ let match;
15385
+ while ((match = re.exec(segment)) !== null) {
15386
+ params[match[1]] = match[2] ?? match[3] ?? "";
15387
+ }
15388
+ return params;
15389
+ }
15390
+ function getWarningsForMppHeader(wwwAuthenticate) {
15391
+ if (!wwwAuthenticate?.trim()) {
15392
+ return [
15393
+ {
15394
+ code: AUDIT_CODES.MPP_HEADER_MISSING,
15395
+ severity: "error",
15396
+ message: "WWW-Authenticate header is absent.",
15397
+ hint: "MPP endpoints must respond to unauthenticated requests with a 402 and a WWW-Authenticate: Payment ... header."
15398
+ }
15399
+ ];
15400
+ }
15401
+ const segments = wwwAuthenticate.split(/,\s*(?=Payment\s)/i).filter((s) => /^Payment\s/i.test(s.trim()));
15402
+ if (segments.length === 0) {
15403
+ return [
15404
+ {
15405
+ code: AUDIT_CODES.MPP_NO_PAYMENT_CHALLENGES,
15406
+ severity: "error",
15407
+ message: "WWW-Authenticate header contains no Payment challenges.",
15408
+ hint: `Add at least one Payment challenge: WWW-Authenticate: Payment method="tempo" intent="charge" realm="..." request='...'`
15409
+ }
15410
+ ];
15411
+ }
15412
+ const warnings = [];
15413
+ for (let i = 0; i < segments.length; i++) {
15414
+ const stripped = segments[i].replace(/^Payment\s+/i, "").trim();
15415
+ const params = parseAuthParams2(stripped);
15416
+ const idx = `WWW-Authenticate[${i}]`;
15417
+ if (!params["id"]) {
15418
+ warnings.push({
15419
+ code: AUDIT_CODES.MPP_CHALLENGE_ID_MISSING,
15420
+ severity: "error",
15421
+ message: `Payment challenge ${i} is missing the id parameter.`,
15422
+ hint: "Set id to a unique challenge identifier so clients can correlate credentials to challenges.",
15423
+ path: `${idx}.id`
15424
+ });
15425
+ }
15426
+ if (!params["method"]) {
15427
+ warnings.push({
15428
+ code: AUDIT_CODES.MPP_CHALLENGE_METHOD_MISSING,
15429
+ severity: "error",
15430
+ message: `Payment challenge ${i} is missing the method parameter.`,
15431
+ hint: 'Set method="tempo" (or your payment method identifier) on the Payment challenge.',
15432
+ path: `${idx}.method`
15433
+ });
15434
+ }
15435
+ if (!params["intent"]) {
15436
+ warnings.push({
15437
+ code: AUDIT_CODES.MPP_CHALLENGE_INTENT_MISSING,
15438
+ severity: "error",
15439
+ message: `Payment challenge ${i} is missing the intent parameter.`,
15440
+ hint: 'Set intent="charge" on the Payment challenge.',
15441
+ path: `${idx}.intent`
15442
+ });
15443
+ }
15444
+ if (!params["realm"]) {
15445
+ warnings.push({
15446
+ code: AUDIT_CODES.MPP_CHALLENGE_REALM_MISSING,
15447
+ severity: "error",
15448
+ message: `Payment challenge ${i} is missing the realm parameter.`,
15449
+ hint: "Set realm to a stable server identifier so clients can associate payment credentials.",
15450
+ path: `${idx}.realm`
15451
+ });
15452
+ }
15453
+ if (!params["expires"]) {
15454
+ warnings.push({
15455
+ code: AUDIT_CODES.MPP_CHALLENGE_EXPIRES_MISSING,
15456
+ severity: "error",
15457
+ message: `Payment challenge ${i} is missing the expires parameter.`,
15458
+ hint: "Set expires to an RFC 3339 timestamp so clients know when the challenge lapses.",
15459
+ path: `${idx}.expires`
15460
+ });
15461
+ }
15462
+ const requestStr = params["request"];
15463
+ if (!requestStr) {
15464
+ warnings.push({
15465
+ code: AUDIT_CODES.MPP_CHALLENGE_REQUEST_MISSING,
15466
+ severity: "error",
15467
+ message: `Payment challenge ${i} is missing the request field.`,
15468
+ hint: `Include a base64url-encoded JSON request field: request=base64url('{"currency":"...","amount":"...","recipient":"..."}')`,
15469
+ path: `${idx}.request`
15470
+ });
15471
+ continue;
15472
+ }
15473
+ let request;
15474
+ try {
15475
+ const decoded = Buffer.from(requestStr, "base64url").toString("utf-8");
15476
+ const parsed = JSON.parse(decoded);
15477
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15478
+ throw new Error("not an object");
15479
+ }
15480
+ request = parsed;
15481
+ } catch {
15482
+ warnings.push({
15483
+ code: AUDIT_CODES.MPP_CHALLENGE_REQUEST_INVALID,
15484
+ severity: "error",
15485
+ message: `Payment challenge ${i} request field is not valid base64url-encoded JSON.`,
15486
+ hint: "The request value must be a base64url-encoded JCS JSON object.",
15487
+ path: `${idx}.request`
15488
+ });
15489
+ continue;
15490
+ }
15491
+ if (!request["currency"]) {
15492
+ warnings.push({
15493
+ code: AUDIT_CODES.MPP_CHALLENGE_ASSET_MISSING,
15494
+ severity: "error",
15495
+ message: `Payment challenge ${i} is missing currency in the request object.`,
15496
+ hint: "Set currency to a TIP-20 token address (e.g. a USDC contract).",
15497
+ path: `${idx}.request.currency`
15498
+ });
15499
+ }
15500
+ const amount = request["amount"];
15501
+ if (amount === void 0 || amount === null) {
15502
+ warnings.push({
15503
+ code: AUDIT_CODES.MPP_CHALLENGE_AMOUNT_MISSING,
15504
+ severity: "error",
15505
+ message: `Payment challenge ${i} is missing amount in the request object.`,
15506
+ hint: 'Set amount to a raw token-unit string (e.g. "1000000" for 1 USDC with 6 decimals).',
15507
+ path: `${idx}.request.amount`
15508
+ });
15509
+ } else if (typeof amount !== "string" && typeof amount !== "number") {
15510
+ warnings.push({
15511
+ code: AUDIT_CODES.MPP_CHALLENGE_AMOUNT_MISSING,
15512
+ severity: "error",
15513
+ message: `Payment challenge ${i} has an invalid amount type (got ${typeof amount}, expected string or number).`,
15514
+ hint: "Set amount to a raw token-unit string.",
15515
+ path: `${idx}.request.amount`
15516
+ });
15517
+ }
15518
+ if (!request["recipient"]) {
15519
+ warnings.push({
15520
+ code: AUDIT_CODES.MPP_CHALLENGE_RECIPIENT_MISSING,
15521
+ severity: "error",
15522
+ message: `Payment challenge ${i} is missing recipient in the request object.`,
15523
+ hint: "Set recipient to the wallet address that should receive payment.",
15524
+ path: `${idx}.request.recipient`
15525
+ });
15526
+ }
15527
+ }
15528
+ return warnings;
15529
+ }
15530
+
15205
15531
  // src/audit/warnings/l3.ts
15206
15532
  function getWarningsFor402Body(body) {
15207
15533
  if (!isRecord(body)) {
@@ -15331,17 +15657,28 @@ function getWarningsForL3(l3) {
15331
15657
  hint: "Add a requestBody or parameters schema so agents can construct valid payloads."
15332
15658
  });
15333
15659
  }
15334
- if (l3.authMode === "paid" && !l3.protocols?.length) {
15660
+ if (l3.authMode === "paid" && l3.source === "openapi" && !l3.protocols?.length) {
15335
15661
  warnings.push({
15336
15662
  code: AUDIT_CODES.L3_PROTOCOLS_MISSING_ON_PAID,
15337
15663
  severity: "info",
15338
15664
  message: "Paid endpoint does not declare supported payment protocols.",
15339
- hint: "Add x-payment-info.protocols (e.g. ['x402']) to the operation."
15665
+ hint: "Add x-payment-info.protocols (e.g. ['x402', 'mpp']) to the operation."
15666
+ });
15667
+ }
15668
+ if (l3.authMode === "paid" && l3.source === "probe" && !l3.paymentOptions?.length) {
15669
+ warnings.push({
15670
+ code: AUDIT_CODES.L3_PAYMENT_OPTIONS_MISSING_ON_PAID,
15671
+ severity: "warn",
15672
+ message: "Paid endpoint did not return payment options in the 402 response.",
15673
+ hint: "Ensure the 402 response returns a valid payment challenge so clients know how to pay."
15340
15674
  });
15341
15675
  }
15342
15676
  if (l3.paymentRequiredBody !== void 0) {
15343
15677
  warnings.push(...getWarningsFor402Body(l3.paymentRequiredBody));
15344
15678
  }
15679
+ if (l3.wwwAuthenticate !== void 0) {
15680
+ warnings.push(...getWarningsForMppHeader(l3.wwwAuthenticate));
15681
+ }
15345
15682
  return warnings;
15346
15683
  }
15347
15684
 
@@ -15560,14 +15897,17 @@ function getWarningsForL4(l4) {
15560
15897
  getL3,
15561
15898
  getL3ForOpenAPI,
15562
15899
  getL3ForProbe,
15900
+ getMppWellKnown,
15563
15901
  getOpenAPI,
15564
15902
  getProbe,
15565
15903
  getWarningsFor402Body,
15566
15904
  getWarningsForL2,
15567
15905
  getWarningsForL3,
15568
15906
  getWarningsForL4,
15907
+ getWarningsForMppHeader,
15569
15908
  getWarningsForOpenAPI,
15570
15909
  getWarningsForWellKnown,
15571
15910
  getWellKnown,
15911
+ getX402WellKnown,
15572
15912
  validatePaymentRequiredDetailed
15573
15913
  });
package/dist/index.d.cts CHANGED
@@ -89,12 +89,18 @@ interface OpenApiRoute {
89
89
  interface WellKnownSource {
90
90
  raw: Record<string, unknown>;
91
91
  routes: WellKnownRoute[];
92
+ title?: string;
93
+ description?: string;
92
94
  instructions?: string;
93
95
  fetchedUrl: string;
96
+ /** Which well-known document(s) this source was built from. */
97
+ protocol: 'x402' | 'mpp' | 'x402+mpp';
94
98
  }
95
99
  interface WellKnownRoute {
96
100
  path: string;
97
101
  method: HttpMethod;
102
+ /** Raw price hint from the well-known document (e.g. MPP `payment.amount`). */
103
+ price?: string;
98
104
  }
99
105
  interface ProbeResult {
100
106
  path: string;
@@ -111,7 +117,7 @@ interface L2Result {
111
117
  description?: string;
112
118
  version?: string;
113
119
  routes: L2Route[];
114
- source: 'openapi' | 'well-known/x402' | null;
120
+ source: 'openapi' | 'well-known/x402' | 'well-known/mpp' | 'well-known/x402+mpp' | null;
115
121
  }
116
122
  interface L2Route {
117
123
  path: string;
@@ -137,10 +143,15 @@ interface L3Result {
137
143
  * and returned a 402. Used by getWarningsForL3 to run full payment-required validation.
138
144
  */
139
145
  paymentRequiredBody?: unknown;
146
+ /**
147
+ * Raw WWW-Authenticate header value from the 402 response. Present when the endpoint
148
+ * was probed and returned a 402 with an MPP challenge. Used by getWarningsForMppHeader.
149
+ */
150
+ wwwAuthenticate?: string;
140
151
  }
141
152
  interface L4Result {
142
153
  guidance: string;
143
- source: 'openapi' | 'well-known/x402';
154
+ source: 'openapi' | 'well-known/x402' | 'well-known/mpp' | 'well-known/x402+mpp';
144
155
  }
145
156
 
146
157
  declare enum GuidanceMode {
@@ -236,8 +247,21 @@ interface FetchError {
236
247
 
237
248
  declare function getOpenAPI(origin: string, headers?: Record<string, string>, signal?: AbortSignal, specificationOverrideUrl?: string): ResultAsync<OpenApiSource | null, FetchError>;
238
249
 
250
+ /**
251
+ * Fetches both `/.well-known/x402` and `/.well-known/mpp` in parallel and merges results.
252
+ *
253
+ * In practice these are mutually exclusive, but if both exist their routes are combined
254
+ * (deduplicated by method+path). x402 wins on instruction/fetchedUrl conflicts.
255
+ *
256
+ * Individual leg failures are treated as "not found" for that leg so valid data from
257
+ * the other is never suppressed. Returns Err(FetchError) only when both legs hard-fail.
258
+ */
239
259
  declare function getWellKnown(origin: string, headers?: Record<string, string>, signal?: AbortSignal): ResultAsync<WellKnownSource | null, FetchError>;
240
260
 
261
+ declare function getX402WellKnown(origin: string, headers?: Record<string, string>, signal?: AbortSignal): ResultAsync<WellKnownSource | null, FetchError>;
262
+
263
+ declare function getMppWellKnown(origin: string, headers?: Record<string, string>, signal?: AbortSignal): ResultAsync<WellKnownSource | null, FetchError>;
264
+
241
265
  declare function getProbe(url: string, headers?: Record<string, string>, signal?: AbortSignal, inputBody?: Record<string, unknown>): ResultAsync<ProbeResult[], FetchError>;
242
266
 
243
267
  declare function checkL2ForOpenAPI(openApi: OpenApiSource): L2Result;
@@ -361,8 +385,21 @@ declare const AUDIT_CODES: {
361
385
  readonly L3_INPUT_SCHEMA_MISSING: "L3_INPUT_SCHEMA_MISSING";
362
386
  readonly L3_AUTH_MODE_MISSING: "L3_AUTH_MODE_MISSING";
363
387
  readonly L3_PROTOCOLS_MISSING_ON_PAID: "L3_PROTOCOLS_MISSING_ON_PAID";
388
+ readonly L3_PAYMENT_OPTIONS_MISSING_ON_PAID: "L3_PAYMENT_OPTIONS_MISSING_ON_PAID";
364
389
  readonly L4_GUIDANCE_MISSING: "L4_GUIDANCE_MISSING";
365
390
  readonly L4_GUIDANCE_TOO_LONG: "L4_GUIDANCE_TOO_LONG";
391
+ readonly MPP_HEADER_MISSING: "MPP_HEADER_MISSING";
392
+ readonly MPP_NO_PAYMENT_CHALLENGES: "MPP_NO_PAYMENT_CHALLENGES";
393
+ readonly MPP_CHALLENGE_ID_MISSING: "MPP_CHALLENGE_ID_MISSING";
394
+ readonly MPP_CHALLENGE_METHOD_MISSING: "MPP_CHALLENGE_METHOD_MISSING";
395
+ readonly MPP_CHALLENGE_INTENT_MISSING: "MPP_CHALLENGE_INTENT_MISSING";
396
+ readonly MPP_CHALLENGE_REALM_MISSING: "MPP_CHALLENGE_REALM_MISSING";
397
+ readonly MPP_CHALLENGE_EXPIRES_MISSING: "MPP_CHALLENGE_EXPIRES_MISSING";
398
+ readonly MPP_CHALLENGE_REQUEST_MISSING: "MPP_CHALLENGE_REQUEST_MISSING";
399
+ readonly MPP_CHALLENGE_REQUEST_INVALID: "MPP_CHALLENGE_REQUEST_INVALID";
400
+ readonly MPP_CHALLENGE_ASSET_MISSING: "MPP_CHALLENGE_ASSET_MISSING";
401
+ readonly MPP_CHALLENGE_AMOUNT_MISSING: "MPP_CHALLENGE_AMOUNT_MISSING";
402
+ readonly MPP_CHALLENGE_RECIPIENT_MISSING: "MPP_CHALLENGE_RECIPIENT_MISSING";
366
403
  };
367
404
  type AuditCode = (typeof AUDIT_CODES)[keyof typeof AUDIT_CODES];
368
405
 
@@ -392,4 +429,18 @@ declare function getWarningsForL3(l3: L3Result | null): AuditWarning[];
392
429
 
393
430
  declare function getWarningsForL4(l4: L4Result | null): AuditWarning[];
394
431
 
395
- export { AUDIT_CODES, type AuditCode, type AuditSeverity, type AuditWarning, type AuthMode, type CheckEndpointNotFound, type CheckEndpointOptions, type CheckEndpointResult, type CheckEndpointSuccess, type DiscoverOriginSchemaNotFound, type DiscoverOriginSchemaOptions, type DiscoverOriginSchemaResult, type DiscoverOriginSchemaSuccess, type EndpointMethodAdvisory, GuidanceMode, type HttpMethod, type L2Result, type L2Route, type L3Result, type L4Result, type MetadataPreview, type MppPaymentOption, type NormalizedAccept, type NormalizedPaymentRequired, type OpenApiRoute, type OpenApiSource, type PaymentOption, type PricingMode, type ProbeResult, type TrustTier, VALIDATION_CODES, type ValidatePaymentRequiredDetailedResult, type ValidatePaymentRequiredOptions, type ValidationIssue, type ValidationSeverity, type ValidationStage, type ValidationSummary, type WellKnownRoute, type WellKnownSource, type X402PaymentOption, type X402V1PaymentOption, type X402V2PaymentOption, attachProbePayload, checkEndpointSchema, checkL2ForOpenAPI, checkL2ForWellknown, checkL4ForOpenAPI, checkL4ForWellknown, discoverOriginSchema, evaluateMetadataCompleteness, getL3, getL3ForOpenAPI, getL3ForProbe, getOpenAPI, getProbe, getWarningsFor402Body, getWarningsForL2, getWarningsForL3, getWarningsForL4, getWarningsForOpenAPI, getWarningsForWellKnown, getWellKnown, validatePaymentRequiredDetailed };
432
+ /**
433
+ * Validates a raw WWW-Authenticate header value from an MPP 402 response and
434
+ * returns issues as AuditWarnings.
435
+ *
436
+ * Checks for:
437
+ * - Header presence
438
+ * - At least one Payment challenge
439
+ * - Required challenge parameters: id, method, intent, realm, expires, request
440
+ * - Valid base64url-encoded JSON in the request field
441
+ * - Required request fields: currency (asset), amount
442
+ * - Recommended request field: recipient (payTo)
443
+ */
444
+ declare function getWarningsForMppHeader(wwwAuthenticate: string | null | undefined): AuditWarning[];
445
+
446
+ export { AUDIT_CODES, type AuditCode, type AuditSeverity, type AuditWarning, type AuthMode, type CheckEndpointNotFound, type CheckEndpointOptions, type CheckEndpointResult, type CheckEndpointSuccess, type DiscoverOriginSchemaNotFound, type DiscoverOriginSchemaOptions, type DiscoverOriginSchemaResult, type DiscoverOriginSchemaSuccess, type EndpointMethodAdvisory, GuidanceMode, type HttpMethod, type L2Result, type L2Route, type L3Result, type L4Result, type MetadataPreview, type MppPaymentOption, type NormalizedAccept, type NormalizedPaymentRequired, type OpenApiRoute, type OpenApiSource, type PaymentOption, type PricingMode, type ProbeResult, type TrustTier, VALIDATION_CODES, type ValidatePaymentRequiredDetailedResult, type ValidatePaymentRequiredOptions, type ValidationIssue, type ValidationSeverity, type ValidationStage, type ValidationSummary, type WellKnownRoute, type WellKnownSource, type X402PaymentOption, type X402V1PaymentOption, type X402V2PaymentOption, attachProbePayload, checkEndpointSchema, checkL2ForOpenAPI, checkL2ForWellknown, checkL4ForOpenAPI, checkL4ForWellknown, discoverOriginSchema, evaluateMetadataCompleteness, getL3, getL3ForOpenAPI, getL3ForProbe, getMppWellKnown, getOpenAPI, getProbe, getWarningsFor402Body, getWarningsForL2, getWarningsForL3, getWarningsForL4, getWarningsForMppHeader, getWarningsForOpenAPI, getWarningsForWellKnown, getWellKnown, getX402WellKnown, validatePaymentRequiredDetailed };