@astrasyncai/verification-gateway 2.4.14 → 2.5.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.
@@ -20,6 +20,65 @@ function hasMinimumAccess(actual, required) {
20
20
  // src/version.ts
21
21
  var SDK_VERSION = "2.4.13";
22
22
 
23
+ // src/well-known.ts
24
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
25
+ var cache = /* @__PURE__ */ new Map();
26
+ var inflight = /* @__PURE__ */ new Map();
27
+ function wellKnownUrl(apiBaseUrl) {
28
+ const base = apiBaseUrl.replace(/\/api\/?$/, "");
29
+ return `${base}/.well-known/agentic-commerce`;
30
+ }
31
+ async function fetchWellKnown(apiBaseUrl) {
32
+ const url = wellKnownUrl(apiBaseUrl);
33
+ const response = await fetch(url, {
34
+ method: "GET",
35
+ headers: { Accept: "application/json" },
36
+ signal: AbortSignal.timeout(5e3)
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error(
40
+ `AstraSync platform must expose /.well-known/agentic-commerce; got ${response.status} from ${url}. SDK cannot initialise without it.`
41
+ );
42
+ }
43
+ const data = await response.json();
44
+ if (!data.registrationUrl || !data.documentationUrl || !data.verifyAccessUrl) {
45
+ throw new Error(
46
+ `/.well-known/agentic-commerce response missing required fields (registrationUrl, documentationUrl, verifyAccessUrl).`
47
+ );
48
+ }
49
+ return data;
50
+ }
51
+ function prefetchWellKnown(apiBaseUrl) {
52
+ const existing = inflight.get(apiBaseUrl);
53
+ if (existing) return existing;
54
+ const promise = fetchWellKnown(apiBaseUrl).then((data) => {
55
+ cache.set(apiBaseUrl, { data, fetchedAt: Date.now() });
56
+ inflight.delete(apiBaseUrl);
57
+ return data;
58
+ }).catch((err) => {
59
+ inflight.delete(apiBaseUrl);
60
+ throw err;
61
+ });
62
+ inflight.set(apiBaseUrl, promise);
63
+ return promise;
64
+ }
65
+ async function getWellKnownUrls(apiBaseUrl) {
66
+ const entry = cache.get(apiBaseUrl);
67
+ if (entry) {
68
+ if (Date.now() - entry.fetchedAt > CACHE_TTL_MS) {
69
+ prefetchWellKnown(apiBaseUrl).catch(() => {
70
+ });
71
+ }
72
+ return entry.data;
73
+ }
74
+ const pending = inflight.get(apiBaseUrl);
75
+ if (pending) return pending;
76
+ return prefetchWellKnown(apiBaseUrl);
77
+ }
78
+ function getCachedWellKnownUrls(apiBaseUrl) {
79
+ return cache.get(apiBaseUrl)?.data;
80
+ }
81
+
23
82
  // src/verify.ts
24
83
  var DEFAULT_CONFIG = {
25
84
  apiBaseUrl: "https://astrasync.ai/api",
@@ -155,21 +214,22 @@ function extractCredentials(headers, query) {
155
214
  }
156
215
  return credentials;
157
216
  }
158
- function createGuidanceResponse(config, reason, options = {}) {
217
+ function createGuidanceResponse(_config, reason, options = {}) {
159
218
  const source = options.source ?? "no_credentials";
160
219
  const isApiError = source === "api_error";
220
+ const urls = options.urls;
161
221
  const guidance = isApiError ? {
162
222
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
163
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
164
- documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
223
+ registrationUrl: urls?.registrationUrl ?? "",
224
+ documentationUrl: urls?.documentationUrl ?? "",
165
225
  steps: [
166
226
  "Retry the request with exponential backoff",
167
227
  "If failures persist, share the correlationId with support"
168
228
  ]
169
229
  } : {
170
230
  message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
171
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
172
- documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
231
+ registrationUrl: urls?.registrationUrl ?? "",
232
+ documentationUrl: urls?.documentationUrl ?? "",
173
233
  steps: [
174
234
  "Register for an AstraSync account",
175
235
  "Create and register your agent",
@@ -211,7 +271,7 @@ async function callVerifyAccessAPI(config, request) {
211
271
  const { credentials, ...requestData } = request;
212
272
  const body = {
213
273
  ...credentials.astraId && { agentId: credentials.astraId },
214
- purpose: requestData.purpose || "general"
274
+ ...requestData.purpose && { purpose: requestData.purpose }
215
275
  };
216
276
  if (requestData.action) body.action = requestData.action;
217
277
  if (requestData.resourceType) body.resourceType = requestData.resourceType;
@@ -291,6 +351,7 @@ async function callVerifyAccessAPI(config, request) {
291
351
  }
292
352
  async function verify(config, request) {
293
353
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
354
+ const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
294
355
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
295
356
  if (mergedConfig.strictInit) {
296
357
  await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
@@ -327,7 +388,8 @@ async function verify(config, request) {
327
388
  if (!apiResponse.success) {
328
389
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
329
390
  source: "api_error",
330
- correlationId: apiResponse.correlationId
391
+ correlationId: apiResponse.correlationId,
392
+ urls
331
393
  });
332
394
  }
333
395
  if (!apiResponse.access?.allowed) {
@@ -350,8 +412,8 @@ async function verify(config, request) {
350
412
  requiresApproval: apiResponse.access?.requiresApproval,
351
413
  guidance: {
352
414
  message: apiResponse.access?.reason || "Access denied by PDLSS policy",
353
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
354
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
415
+ registrationUrl: urls?.registrationUrl ?? "",
416
+ documentationUrl: urls?.documentationUrl ?? ""
355
417
  },
356
418
  verifiedAt: /* @__PURE__ */ new Date(),
357
419
  // Extract sessionId so decisions can be recorded for denials too
@@ -422,12 +484,12 @@ async function verify(config, request) {
422
484
  ];
423
485
  result.guidance = result.runtimeChallenge ? {
424
486
  message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
425
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
426
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
487
+ registrationUrl: urls?.registrationUrl ?? "",
488
+ documentationUrl: urls?.documentationUrl ?? ""
427
489
  } : {
428
490
  message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
429
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
430
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
491
+ registrationUrl: urls?.registrationUrl ?? "",
492
+ documentationUrl: urls?.documentationUrl ?? ""
431
493
  };
432
494
  } else if (result.recommendation === "step_up_required") {
433
495
  result.requiresStepUp = true;
@@ -563,19 +625,22 @@ function extractFromMcpBody(astrasyncMeta, args, key) {
563
625
  }
564
626
  return { value: void 0, source: void 0 };
565
627
  }
566
- function mcpToPdlss(parsed, headerPurpose, headerAction) {
567
- const resource = parsed.toolName ? `mcp:tool/${parsed.toolName}` : `mcp:method/${parsed.method}`;
628
+ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate) {
629
+ const resource = toolGate?.resource ?? requestPath;
568
630
  let purpose;
569
631
  let purposeSource;
570
- if (headerPurpose) {
632
+ if (toolGate?.purpose !== void 0) {
633
+ purpose = toolGate.purpose;
634
+ purposeSource = "tool_gate";
635
+ } else if (headerPurpose) {
571
636
  purpose = headerPurpose;
572
637
  purposeSource = "header";
573
638
  } else if (parsed.purposeFromBody && parsed.purposeSourceFromBody) {
574
639
  purpose = parsed.purposeFromBody;
575
640
  purposeSource = parsed.purposeSourceFromBody;
576
641
  } else {
577
- purpose = "mcp_invoke";
578
- purposeSource = "default_mcp_invoke";
642
+ purpose = void 0;
643
+ purposeSource = void 0;
579
644
  }
580
645
  let action;
581
646
  let actionSource;
@@ -600,6 +665,9 @@ function mcpRiskTier(parsed) {
600
665
  }
601
666
 
602
667
  // src/adapters/mcp.ts
668
+ function normalizeToolGate(gate) {
669
+ return typeof gate === "string" ? { minAccessLevel: gate } : gate;
670
+ }
603
671
  function readSingleHeader(value) {
604
672
  if (typeof value === "string") return value;
605
673
  if (Array.isArray(value)) return value[0];
@@ -615,6 +683,9 @@ function dedupeFailures(result) {
615
683
  return true;
616
684
  });
617
685
  }
686
+ if (result.denialReasons && result.denialReasons.length > 1) {
687
+ result.denialReasons = [...new Set(result.denialReasons)];
688
+ }
618
689
  }
619
690
  function defaultMcpDenied(result, req, res) {
620
691
  const id = req.body?.id ?? null;
@@ -640,11 +711,17 @@ function defaultMcpDenied(result, req, res) {
640
711
  });
641
712
  }
642
713
  function resolveMinAccessLevel(parsed, opts) {
643
- if (parsed.toolName && opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
644
- return { level: opts.toolGates[parsed.toolName], source: "toolGate" };
714
+ if (!parsed.toolName) {
715
+ if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
716
+ return { level: opts.methodGates[parsed.method], source: "methodGate" };
717
+ }
718
+ return { level: "none", source: "discovery_default" };
645
719
  }
646
- if (opts.methodGates && opts.methodGates[parsed.method] !== void 0) {
647
- return { level: opts.methodGates[parsed.method], source: "methodGate" };
720
+ if (opts.toolGates && opts.toolGates[parsed.toolName] !== void 0) {
721
+ return {
722
+ level: normalizeToolGate(opts.toolGates[parsed.toolName]).minAccessLevel,
723
+ source: "toolGate"
724
+ };
648
725
  }
649
726
  return { level: mcpRiskTier(parsed), source: "tier" };
650
727
  }
@@ -662,6 +739,10 @@ function createMcpMiddleware(options) {
662
739
  failOnError = "open",
663
740
  ...config
664
741
  } = options;
742
+ if (config.apiBaseUrl) {
743
+ prefetchWellKnown(config.apiBaseUrl).catch(() => {
744
+ });
745
+ }
665
746
  return async (req, res, next) => {
666
747
  try {
667
748
  if (skip) return next();
@@ -674,6 +755,7 @@ function createMcpMiddleware(options) {
674
755
  return next();
675
756
  }
676
757
  req.mcpRequest = parsed;
758
+ const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
677
759
  const headerRaw = req.headers["x-astra-id"] ?? req.headers["x-astra-agentid"];
678
760
  const headerAstraId = typeof headerRaw === "string" ? headerRaw : Array.isArray(headerRaw) ? headerRaw[0] : void 0;
679
761
  const bodyAstraId = parsed.agentIdFromBody;
@@ -727,9 +809,17 @@ function createMcpMiddleware(options) {
727
809
  }
728
810
  return next();
729
811
  }
812
+ const rawGate = parsed.toolName && toolGates?.[parsed.toolName];
813
+ const gate = rawGate ? normalizeToolGate(rawGate) : void 0;
730
814
  const headerPurpose = readSingleHeader(req.headers["x-astra-purpose"]);
731
815
  const headerAction = readSingleHeader(req.headers["x-astra-action"]);
732
- const pdlss = mcpToPdlss(parsed, headerPurpose, headerAction);
816
+ const pdlss = mcpToPdlss(
817
+ parsed,
818
+ req.path,
819
+ headerPurpose,
820
+ headerAction,
821
+ gate ? { purpose: gate.purpose, resource: gate.resource } : void 0
822
+ );
733
823
  if (config.debug) {
734
824
  console.debug("[mcp-middleware] pdlss resolved", {
735
825
  purpose_source: pdlss.purposeSource,
@@ -790,6 +880,13 @@ function createMcpMiddleware(options) {
790
880
  };
791
881
  result.failures = [...result.failures ?? [], insufficientFailure];
792
882
  result.denialReasons = [...result.denialReasons ?? [], insufficientFailure.message];
883
+ if (!result.guidance && wellKnownUrls) {
884
+ result.guidance = {
885
+ message: insufficientFailure.message,
886
+ registrationUrl: wellKnownUrls.registrationUrl,
887
+ documentationUrl: wellKnownUrls.documentationUrl
888
+ };
889
+ }
793
890
  if (shouldRecordDecisions) {
794
891
  const overrideKind = gateSource === "toolGate" ? "toolGate" : gateSource === "methodGate" ? "methodGate" : "other";
795
892
  const override = {
@@ -858,6 +955,14 @@ function createMcpMiddleware(options) {
858
955
  verifiedAt: /* @__PURE__ */ new Date(),
859
956
  correlationId
860
957
  };
958
+ const catchUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
959
+ if (catchUrls) {
960
+ result.guidance = {
961
+ message: `Middleware threw ${errorClass} \u2014 failing closed`,
962
+ registrationUrl: catchUrls.registrationUrl,
963
+ documentationUrl: catchUrls.documentationUrl
964
+ };
965
+ }
861
966
  dedupeFailures(result);
862
967
  return onDenied(result, req, res);
863
968
  }