@adcp/sdk 15.4.0 → 15.6.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.
Files changed (164) hide show
  1. package/README.md +15 -0
  2. package/dist/lib/advanced.d.mts +2 -2
  3. package/dist/lib/advanced.d.ts +2 -2
  4. package/dist/lib/advanced.js +8 -2
  5. package/dist/lib/advanced.mjs +8 -2
  6. package/dist/lib/auth/oauth/index.d.mts +1 -0
  7. package/dist/lib/auth/oauth/index.d.ts +1 -0
  8. package/dist/lib/auth/oauth/index.js +5 -0
  9. package/dist/lib/auth/oauth/index.mjs +3 -0
  10. package/dist/lib/auth/oauth/pending-web-flow.d.mts +16 -0
  11. package/dist/lib/auth/oauth/pending-web-flow.d.ts +16 -0
  12. package/dist/lib/auth/oauth/pending-web-flow.js +66 -0
  13. package/dist/lib/auth/oauth/pending-web-flow.mjs +41 -0
  14. package/dist/lib/auth/oauth/web-flow.js +3 -3
  15. package/dist/lib/auth/oauth/web-flow.mjs +3 -3
  16. package/dist/lib/core/AgentClient.d.mts +4 -0
  17. package/dist/lib/core/AgentClient.d.ts +4 -0
  18. package/dist/lib/core/AgentClient.js +8 -0
  19. package/dist/lib/core/AgentClient.mjs +8 -0
  20. package/dist/lib/core/SingleAgentClient.d.mts +154 -0
  21. package/dist/lib/core/SingleAgentClient.d.ts +154 -0
  22. package/dist/lib/core/SingleAgentClient.js +661 -23
  23. package/dist/lib/core/SingleAgentClient.mjs +666 -23
  24. package/dist/lib/core/agent-discovery-cache.d.mts +103 -0
  25. package/dist/lib/core/agent-discovery-cache.d.ts +103 -0
  26. package/dist/lib/core/agent-discovery-cache.js +121 -0
  27. package/dist/lib/core/agent-discovery-cache.mjs +92 -0
  28. package/dist/lib/index.d.mts +5 -3
  29. package/dist/lib/index.d.ts +5 -3
  30. package/dist/lib/index.js +7 -0
  31. package/dist/lib/index.mjs +6 -0
  32. package/dist/lib/protocols/index.d.mts +2 -2
  33. package/dist/lib/protocols/index.d.ts +2 -2
  34. package/dist/lib/protocols/index.js +1 -1
  35. package/dist/lib/protocols/index.mjs +1 -1
  36. package/dist/lib/protocols/mcp-call-context.d.mts +109 -0
  37. package/dist/lib/protocols/mcp-call-context.d.ts +109 -0
  38. package/dist/lib/protocols/mcp-call-context.js +221 -0
  39. package/dist/lib/protocols/mcp-call-context.mjs +186 -0
  40. package/dist/lib/protocols/mcp-modern.d.mts +9 -1
  41. package/dist/lib/protocols/mcp-modern.d.ts +9 -1
  42. package/dist/lib/protocols/mcp-modern.js +180 -104
  43. package/dist/lib/protocols/mcp-modern.mjs +193 -104
  44. package/dist/lib/protocols/mcp-scope.d.mts +15 -0
  45. package/dist/lib/protocols/mcp-scope.d.ts +15 -0
  46. package/dist/lib/protocols/mcp.d.mts +4 -2
  47. package/dist/lib/protocols/mcp.d.ts +4 -2
  48. package/dist/lib/protocols/mcp.js +178 -154
  49. package/dist/lib/protocols/mcp.mjs +189 -153
  50. package/dist/lib/protocols/transportDiagnostics.d.mts +33 -1
  51. package/dist/lib/protocols/transportDiagnostics.d.ts +33 -1
  52. package/dist/lib/protocols/transportDiagnostics.js +128 -12
  53. package/dist/lib/protocols/transportDiagnostics.mjs +128 -12
  54. package/dist/lib/registry/types.generated.d.mts +63 -1
  55. package/dist/lib/registry/types.generated.d.ts +63 -1
  56. package/dist/lib/reporting/azure/index.d.mts +26 -0
  57. package/dist/lib/reporting/azure/index.d.ts +26 -0
  58. package/dist/lib/reporting/azure/index.js +247 -0
  59. package/dist/lib/reporting/azure/index.mjs +230 -0
  60. package/dist/lib/reporting/bigquery/index.d.mts +127 -0
  61. package/dist/lib/reporting/bigquery/index.d.ts +127 -0
  62. package/dist/lib/reporting/bigquery/index.js +379 -0
  63. package/dist/lib/reporting/bigquery/index.mjs +341 -0
  64. package/dist/lib/reporting/gcs/index.d.mts +2 -0
  65. package/dist/lib/reporting/gcs/index.d.ts +2 -0
  66. package/dist/lib/reporting/gcs/index.js +3 -0
  67. package/dist/lib/reporting/gcs/index.mjs +2 -0
  68. package/dist/lib/reporting/gcs/row-provider.d.mts +32 -0
  69. package/dist/lib/reporting/gcs/row-provider.d.ts +32 -0
  70. package/dist/lib/reporting/gcs/row-provider.js +205 -0
  71. package/dist/lib/reporting/gcs/row-provider.mjs +188 -0
  72. package/dist/lib/reporting/ledger/change-feed.d.mts +41 -0
  73. package/dist/lib/reporting/ledger/change-feed.d.ts +41 -0
  74. package/dist/lib/reporting/ledger/change-feed.js +101 -0
  75. package/dist/lib/reporting/ledger/change-feed.mjs +74 -0
  76. package/dist/lib/reporting/ledger/handler.js +22 -8
  77. package/dist/lib/reporting/ledger/handler.mjs +22 -8
  78. package/dist/lib/reporting/ledger/index.d.mts +4 -0
  79. package/dist/lib/reporting/ledger/index.d.ts +4 -0
  80. package/dist/lib/reporting/ledger/index.js +9 -1
  81. package/dist/lib/reporting/ledger/index.mjs +4 -0
  82. package/dist/lib/reporting/ledger/managed-postgres.d.mts +2 -0
  83. package/dist/lib/reporting/ledger/managed-postgres.d.ts +2 -0
  84. package/dist/lib/reporting/ledger/managed-postgres.js +21 -1
  85. package/dist/lib/reporting/ledger/managed-postgres.mjs +21 -1
  86. package/dist/lib/reporting/ledger/managed.d.mts +9 -0
  87. package/dist/lib/reporting/ledger/managed.d.ts +9 -0
  88. package/dist/lib/reporting/ledger/managed.js +18 -0
  89. package/dist/lib/reporting/ledger/managed.mjs +18 -0
  90. package/dist/lib/reporting/ledger/postgres.d.mts +185 -0
  91. package/dist/lib/reporting/ledger/postgres.d.ts +185 -0
  92. package/dist/lib/reporting/ledger/postgres.js +872 -26
  93. package/dist/lib/reporting/ledger/postgres.mjs +880 -26
  94. package/dist/lib/reporting/ledger/producer.js +10 -1
  95. package/dist/lib/reporting/ledger/producer.mjs +10 -1
  96. package/dist/lib/reporting/ledger/row-storage-errors.d.mts +7 -0
  97. package/dist/lib/reporting/ledger/row-storage-errors.d.ts +7 -0
  98. package/dist/lib/reporting/ledger/row-storage-errors.js +41 -0
  99. package/dist/lib/reporting/ledger/row-storage-errors.mjs +16 -0
  100. package/dist/lib/reporting/ledger/row-storage-migration.d.mts +41 -0
  101. package/dist/lib/reporting/ledger/row-storage-migration.d.ts +41 -0
  102. package/dist/lib/reporting/ledger/row-storage-migration.js +234 -0
  103. package/dist/lib/reporting/ledger/row-storage-migration.mjs +208 -0
  104. package/dist/lib/reporting/ledger/row-storage-object.d.mts +112 -0
  105. package/dist/lib/reporting/ledger/row-storage-object.d.ts +112 -0
  106. package/dist/lib/reporting/ledger/row-storage-object.js +304 -0
  107. package/dist/lib/reporting/ledger/row-storage-object.mjs +262 -0
  108. package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +44 -0
  109. package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +44 -0
  110. package/dist/lib/reporting/ledger/row-storage-provider-io.js +142 -0
  111. package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +111 -0
  112. package/dist/lib/reporting/ledger/row-storage.d.mts +224 -0
  113. package/dist/lib/reporting/ledger/row-storage.d.ts +224 -0
  114. package/dist/lib/reporting/ledger/row-storage.js +692 -0
  115. package/dist/lib/reporting/ledger/row-storage.mjs +680 -0
  116. package/dist/lib/reporting/ledger/types.d.mts +8 -0
  117. package/dist/lib/reporting/ledger/types.d.ts +8 -0
  118. package/dist/lib/reporting/s3/index.d.mts +29 -0
  119. package/dist/lib/reporting/s3/index.d.ts +29 -0
  120. package/dist/lib/reporting/s3/index.js +324 -0
  121. package/dist/lib/reporting/s3/index.mjs +317 -0
  122. package/dist/lib/reporting/service/index.d.mts +43 -1
  123. package/dist/lib/reporting/service/index.d.ts +43 -1
  124. package/dist/lib/reporting/service/index.js +69 -1
  125. package/dist/lib/reporting/service/index.mjs +71 -1
  126. package/dist/lib/schemas/browser.d.mts +14 -0
  127. package/dist/lib/schemas/browser.d.ts +14 -0
  128. package/dist/lib/schemas/browser.js +48 -0
  129. package/dist/lib/schemas/browser.mjs +20 -0
  130. package/dist/lib/schemas/index.d.mts +2 -120
  131. package/dist/lib/schemas/index.d.ts +2 -120
  132. package/dist/lib/schemas/index.js +3 -11
  133. package/dist/lib/schemas/index.mjs +1 -9
  134. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  135. package/dist/lib/testing/index.d.mts +1 -0
  136. package/dist/lib/testing/index.d.ts +1 -0
  137. package/dist/lib/testing/index.js +3 -0
  138. package/dist/lib/testing/index.mjs +2 -0
  139. package/dist/lib/testing/pending-web-flow-store.d.mts +20 -0
  140. package/dist/lib/testing/pending-web-flow-store.d.ts +20 -0
  141. package/dist/lib/testing/pending-web-flow-store.js +132 -0
  142. package/dist/lib/testing/pending-web-flow-store.mjs +98 -0
  143. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  144. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  145. package/dist/lib/validation/get-products-request.d.mts +122 -0
  146. package/dist/lib/validation/get-products-request.d.ts +122 -0
  147. package/dist/lib/validation/get-products-request.js +39 -0
  148. package/dist/lib/validation/get-products-request.mjs +14 -0
  149. package/dist/lib/version.d.mts +3 -3
  150. package/dist/lib/version.d.ts +3 -3
  151. package/dist/lib/version.js +3 -3
  152. package/dist/lib/version.mjs +3 -3
  153. package/docs/README.md +1 -1
  154. package/docs/TYPE-SUMMARY.md +35 -2
  155. package/docs/ZOD-SCHEMAS.md +13 -1
  156. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +142 -0
  157. package/docs/guides/EXISTING-PLATFORM.md +43 -12
  158. package/docs/guides/OAUTH-ISSUER-BINDING.md +6 -0
  159. package/docs/guides/REPORTING-LEDGER.md +374 -0
  160. package/docs/guides/REPORTING-OPERATIONS.md +76 -1
  161. package/docs/guides/WEB-OAUTH.md +48 -1
  162. package/docs/llms.txt +4 -2
  163. package/docs/migration-14.x-rc-worksheet.md +6 -4
  164. package/package.json +64 -1
@@ -43,6 +43,7 @@ import { withTaskDeadline } from "./task-deadline.mjs";
43
43
  import { createMCPRequestHeaders } from "../auth/index.mjs";
44
44
  import { isAbortOrTimeoutError } from "../protocols/abort.mjs";
45
45
  import { getSignedRequestRejection } from "../protocols/signedRequestRejection.mjs";
46
+ import { withAmbientIdentityHeaders } from "../protocols/mcp-call-context.mjs";
46
47
  import { ProtocolClient, normalizeTransportOptions } from "../protocols/index.mjs";
47
48
  import {
48
49
  AuthenticationCredentialsRejectedError,
@@ -113,6 +114,12 @@ function presentedStaticTransportCredential(authToken, headers, authProvider) {
113
114
  return normalized === "authorization" || normalized === "x-adcp-auth";
114
115
  });
115
116
  }
117
+ import {
118
+ agentDiscoveryCacheKey,
119
+ MAX_AGENT_DISCOVERY_TTL_MS,
120
+ resolveAgentDiscoveryTtlMs,
121
+ validateAgentDiscoveryCacheConfig
122
+ } from "./agent-discovery-cache.mjs";
116
123
  import {
117
124
  buildSyntheticCapabilities,
118
125
  buildSyntheticV3Capabilities,
@@ -726,6 +733,30 @@ const DURABLE_SETTLEMENT_TERMINAL_STATUSES = /* @__PURE__ */ new Set([
726
733
  "aborted"
727
734
  ]);
728
735
  const MCP_WEBHOOK_REQUIRED_FIELDS = ["idempotency_key", "task_id", "task_type", "status", "timestamp"];
736
+ const sharedInvalidations = /* @__PURE__ */ new WeakMap();
737
+ const sharedInvalidationFloors = /* @__PURE__ */ new WeakMap();
738
+ let sharedInvalidationCounter = 0;
739
+ const MAX_SHARED_INVALIDATION_RECORDS = 1024;
740
+ const sharedMutationChains = /* @__PURE__ */ new WeakMap();
741
+ const MAX_PENDING_SHARED_MUTATIONS_PER_KEY = 16;
742
+ const MAX_SHARED_MUTATION_KEYS = 1024;
743
+ const sharedDiscoveryFlights = /* @__PURE__ */ new WeakMap();
744
+ const MAX_SHARED_DISCOVERY_WAITS = 3;
745
+ const SHARED_DISCOVERY_CACHE_TIMEOUT_MS = 2e3;
746
+ const SHARED_DISCOVERY_CLOCK_SKEW_MS = 5e3;
747
+ const CAPABILITY_STALE_ERROR_CODES = /* @__PURE__ */ new Set([
748
+ "VERSION_UNSUPPORTED",
749
+ "UNSUPPORTED_FEATURE",
750
+ "FEATURE_UNSUPPORTED",
751
+ "TOOL_NOT_FOUND",
752
+ "METHOD_NOT_FOUND",
753
+ "UNKNOWN_TOOL"
754
+ ]);
755
+ function isToolSchemaRecord(value) {
756
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every(
757
+ (properties) => typeof properties === "object" && properties !== null && !Array.isArray(properties)
758
+ );
759
+ }
729
760
  function isCapabilityEvidence(value) {
730
761
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
731
762
  const candidate = value;
@@ -823,6 +854,7 @@ class SingleAgentClient {
823
854
  ...configuredWebhookVerification !== void 0 && { webhookVerification: configuredWebhookVerification }
824
855
  };
825
856
  config = this.config;
857
+ validateAgentDiscoveryCacheConfig(config.discoveryCache);
826
858
  this.resolvedAdcpVersion = resolveAdcpVersion(config.adcpVersion);
827
859
  this.webhookRegistrationStore = config.webhookRegistrationStore ?? new InMemoryWebhookRegistrationStore();
828
860
  this.webhookReplayStore = config.webhookVerification?.replayStore ?? new InMemoryReplayStore();
@@ -1101,6 +1133,20 @@ class SingleAgentClient {
1101
1133
  // Freshness bound for application-owned evidence only
1102
1134
  capabilityEvidenceScopeKey = randomUUID();
1103
1135
  capabilityEvidenceAuthMaterial;
1136
+ capabilitiesFromSharedCache = false;
1137
+ // Installed from config.discoveryCache rather than discovered live
1138
+ discoveryEpoch = 0;
1139
+ // bumped by invalidation so an older in-flight discovery cannot re-install what was dropped
1140
+ discoveredEndpointKey;
1141
+ // discoveryCache key the local endpoint was obtained under
1142
+ discoveredEndpointEpoch;
1143
+ // shared invalidation epoch of that key when the endpoint was obtained
1144
+ discoveredEndpointExpiresAt;
1145
+ // freshness bound for that endpoint (shared-cache mode only)
1146
+ sharedEvidenceEpoch;
1147
+ // shared invalidation epoch of that key when the evidence was obtained
1148
+ sharedEvidenceKey;
1149
+ // discoveryCache key the local capability cache was obtained under
1104
1150
  _v2WarningFired = false;
1105
1151
  // Gate: emit the v2-sunset warning once per client instance
1106
1152
  _syntheticV3WarningFired = false;
@@ -1772,19 +1818,34 @@ class SingleAgentClient {
1772
1818
  if (!needsDiscovery) {
1773
1819
  return this.normalizedAgent;
1774
1820
  }
1821
+ const effectiveEndpointKey = this.config.discoveryCache && !usesScopedFetch && this.discoveredEndpoint ? this.sharedDiscoveryKey(options) : void 0;
1822
+ if (this.config.discoveryCache && !usesScopedFetch && this.discoveredEndpoint && (this.discoveredEndpointKey !== effectiveEndpointKey || effectiveEndpointKey !== void 0 && this.discoveredEndpointEpoch !== this.sharedInvalidationEpoch(effectiveEndpointKey) || (this.discoveredEndpointExpiresAt ?? 0) <= Date.now())) {
1823
+ this.discoveredEndpoint = void 0;
1824
+ this.discoveredAgent = void 0;
1825
+ this.discoveredMcpEra = void 0;
1826
+ }
1775
1827
  if (!usesScopedFetch && this.discoveredAgent) {
1776
1828
  return this.discoveredAgent;
1777
1829
  }
1830
+ if (!usesScopedFetch && !this.discoveredEndpoint && this.config.discoveryCache) {
1831
+ await this.adoptSharedEndpoint(options);
1832
+ if (this.discoveredEndpoint && (this.discoveredEndpointEpoch !== this.sharedInvalidationEpoch(this.sharedDiscoveryKey(options)) || (this.discoveredEndpointExpiresAt ?? 0) <= Date.now())) {
1833
+ this.discoveredEndpoint = void 0;
1834
+ this.discoveredAgent = void 0;
1835
+ this.discoveredMcpEra = void 0;
1836
+ }
1837
+ }
1778
1838
  if (!usesScopedFetch && this.discoveredEndpoint) {
1779
- this.discoveredAgent = {
1839
+ const adoptedAgent = {
1780
1840
  ...this.normalizedAgent,
1781
1841
  agent_uri: this.discoveredEndpoint
1782
1842
  };
1843
+ this.discoveredAgent = adoptedAgent;
1783
1844
  if (this.normalizedAgent.oauth_tokens && !this.normalizedAgent.oauth_client_credentials) {
1784
1845
  const { shareNonInteractiveOAuthProvider } = await import("../auth/oauth/provider-cache.mjs");
1785
- shareNonInteractiveOAuthProvider(this.normalizedAgent, this.discoveredAgent);
1846
+ shareNonInteractiveOAuthProvider(this.normalizedAgent, adoptedAgent);
1786
1847
  }
1787
- return this.discoveredAgent;
1848
+ return adoptedAgent;
1788
1849
  }
1789
1850
  if (this.normalizedAgent.oauth_client_credentials) {
1790
1851
  const { ensureClientCredentialsTokens, getAgentStorage } = await import("../auth/oauth/index.mjs");
@@ -1795,26 +1856,59 @@ class SingleAgentClient {
1795
1856
  signal: options?.signal
1796
1857
  });
1797
1858
  }
1859
+ const endpointEpoch = this.discoveryEpochSnapshot(options);
1798
1860
  const discoveredEndpoint = await this.discoverMCPEndpoint(this.normalizedAgent.agent_uri, options);
1799
1861
  if (usesScopedFetch) {
1800
- const discoveredAgent = { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1862
+ const discoveredAgent2 = { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1801
1863
  if (this.normalizedAgent.oauth_tokens && !this.normalizedAgent.oauth_client_credentials) {
1802
1864
  const { shareNonInteractiveOAuthProvider } = await import("../auth/oauth/provider-cache.mjs");
1803
- shareNonInteractiveOAuthProvider(this.normalizedAgent, discoveredAgent);
1865
+ shareNonInteractiveOAuthProvider(this.normalizedAgent, discoveredAgent2);
1804
1866
  }
1805
- return discoveredAgent;
1867
+ return discoveredAgent2;
1868
+ }
1869
+ if (endpointEpoch && !this.discoveryEpochIs(endpointEpoch)) {
1870
+ return { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1806
1871
  }
1807
1872
  this.discoveredEndpoint = discoveredEndpoint;
1808
- this.canonicalBaseUrl = this.computeBaseUrl(this.discoveredEndpoint);
1809
- this.discoveredAgent = {
1873
+ if (this.config.discoveryCache) {
1874
+ this.discoveredEndpointKey = this.sharedDiscoveryKey(options);
1875
+ this.discoveredEndpointEpoch = endpointEpoch?.shared;
1876
+ this.discoveredEndpointExpiresAt = Date.now() + resolveAgentDiscoveryTtlMs(this.config.discoveryCache.ttlMs);
1877
+ const stored = await this.writeSharedDiscovery(
1878
+ options,
1879
+ {
1880
+ endpoint: {
1881
+ agentUri: discoveredEndpoint,
1882
+ ...this.discoveredMcpEra && { mcpEra: this.discoveredMcpEra }
1883
+ }
1884
+ },
1885
+ false,
1886
+ endpointEpoch
1887
+ );
1888
+ if (endpointEpoch && !this.discoveryEpochIs(endpointEpoch)) {
1889
+ if (this.discoveredEndpoint === discoveredEndpoint && this.discoveredEndpointEpoch === endpointEpoch.shared) {
1890
+ this.discoveredEndpoint = void 0;
1891
+ this.discoveredEndpointKey = void 0;
1892
+ this.discoveredEndpointEpoch = void 0;
1893
+ this.discoveredEndpointExpiresAt = void 0;
1894
+ if (this.discoveredAgent?.agent_uri === discoveredEndpoint) this.discoveredAgent = void 0;
1895
+ this.discoveredMcpEra = void 0;
1896
+ }
1897
+ return { ...this.normalizedAgent, agent_uri: discoveredEndpoint };
1898
+ }
1899
+ if (stored && this.discoveredEndpoint === discoveredEndpoint) this.discoveredEndpointExpiresAt = stored.expiresAt;
1900
+ }
1901
+ this.canonicalBaseUrl = this.computeBaseUrl(discoveredEndpoint);
1902
+ const discoveredAgent = {
1810
1903
  ...this.normalizedAgent,
1811
- agent_uri: this.discoveredEndpoint
1904
+ agent_uri: discoveredEndpoint
1812
1905
  };
1906
+ this.discoveredAgent = discoveredAgent;
1813
1907
  if (this.normalizedAgent.oauth_tokens && !this.normalizedAgent.oauth_client_credentials) {
1814
1908
  const { shareNonInteractiveOAuthProvider } = await import("../auth/oauth/provider-cache.mjs");
1815
- shareNonInteractiveOAuthProvider(this.normalizedAgent, this.discoveredAgent);
1909
+ shareNonInteractiveOAuthProvider(this.normalizedAgent, discoveredAgent);
1816
1910
  }
1817
- return this.discoveredAgent;
1911
+ return discoveredAgent;
1818
1912
  }
1819
1913
  /**
1820
1914
  * Ensure A2A canonical URL is resolved (lazy initialization)
@@ -3284,6 +3378,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
3284
3378
  )
3285
3379
  );
3286
3380
  throwIfAborted(effectiveOptions?.signal);
3381
+ if (!result.success) await this.invalidateDiscoveryOnCapabilityError(result.adcpError?.code, effectiveOptions);
3287
3382
  const postAdapterLogs = [...inputSchemaStripLogs, ...v25DriftLogs];
3288
3383
  if (postAdapterLogs.length > 0) {
3289
3384
  result.debug_logs = [...result.debug_logs ?? [], ...postAdapterLogs];
@@ -5792,6 +5887,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
5792
5887
  beforeDispatch,
5793
5888
  deferredClientContext
5794
5889
  );
5890
+ if (!result.success) await this.invalidateDiscoveryOnCapabilityError(result.adcpError?.code, effectiveOptions);
5795
5891
  const postAdapterLogs = [...inputSchemaStripLogs, ...v25DriftLogs];
5796
5892
  if (postAdapterLogs.length > 0) {
5797
5893
  result.debug_logs = [...result.debug_logs ?? [], ...postAdapterLogs];
@@ -6357,6 +6453,26 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6357
6453
  tools
6358
6454
  };
6359
6455
  }
6456
+ const { withScopedLegacyConnection } = await import("../protocols/mcp.mjs");
6457
+ const scopedList = await withScopedLegacyConnection(
6458
+ connectOptions,
6459
+ "tools/list",
6460
+ (client) => withResponseSizeLimit(maxResponseBytes, () => client.listTools(mcpListParams, mcpRequestOptions))
6461
+ );
6462
+ if (scopedList) {
6463
+ return {
6464
+ name: this.normalizedAgent.name,
6465
+ description: void 0,
6466
+ protocol: this.normalizedAgent.protocol,
6467
+ url: agent.agent_uri,
6468
+ tools: scopedList.value.tools.map((tool) => ({
6469
+ name: tool.name,
6470
+ description: tool.description,
6471
+ inputSchema: tool.inputSchema,
6472
+ parameters: tool.inputSchema?.properties ? Object.keys(tool.inputSchema.properties) : []
6473
+ }))
6474
+ };
6475
+ }
6360
6476
  const { client: mcpClient } = await connectMCP(connectOptions);
6361
6477
  try {
6362
6478
  const toolsList = await withResponseSizeLimit(
@@ -6470,12 +6586,98 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6470
6586
  this.cachedCapabilities = void 0;
6471
6587
  this.cachedToolSchemas = void 0;
6472
6588
  this.primedCapabilitiesExpiresAt = void 0;
6589
+ this.capabilitiesFromSharedCache = false;
6590
+ }
6591
+ const effectiveSharedKey = this.config.discoveryCache && !usesScopedFetch && this.cachedCapabilities ? this.sharedDiscoveryKey(options) : void 0;
6592
+ if (this.config.discoveryCache && !usesScopedFetch && this.cachedCapabilities && (this.sharedEvidenceKey !== effectiveSharedKey || effectiveSharedKey !== void 0 && this.sharedEvidenceEpoch !== void 0 && this.sharedEvidenceEpoch !== this.sharedInvalidationEpoch(effectiveSharedKey))) {
6593
+ this.cachedCapabilities = void 0;
6594
+ this.cachedToolSchemas = void 0;
6595
+ this.primedCapabilitiesExpiresAt = void 0;
6596
+ this.capabilitiesFromSharedCache = false;
6473
6597
  }
6474
6598
  if (!usesScopedFetch && this.cachedCapabilities) {
6475
6599
  if (discoveryContext) discoveryContext.toolSchemas = this.cachedToolSchemas;
6476
6600
  this.maybeWarnV2Sunset(this.cachedCapabilities);
6477
6601
  return this.cachedCapabilities;
6478
6602
  }
6603
+ if (!usesScopedFetch && this.config.discoveryCache) {
6604
+ return this.discoverCapabilitiesThroughSharedCache(options, usesScopedFetch, discoveryContext);
6605
+ }
6606
+ return this.discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext);
6607
+ }
6608
+ /**
6609
+ * Serve capabilities from the shared `discoveryCache`, or discover them live
6610
+ * as the single in-process "leader" for this cache key. Concurrent clients
6611
+ * for the same key wait for the leader under their *own* signal and deadline;
6612
+ * a leader that fails or is cancelled never fails its followers, which then
6613
+ * discover for themselves.
6614
+ */
6615
+ async discoverCapabilitiesThroughSharedCache(options, usesScopedFetch, discoveryContext) {
6616
+ const config = this.config.discoveryCache;
6617
+ const key = this.sharedDiscoveryKey(options);
6618
+ if (!key) return this.discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext);
6619
+ let flights = sharedDiscoveryFlights.get(config.cache);
6620
+ if (!flights) {
6621
+ flights = /* @__PURE__ */ new Map();
6622
+ sharedDiscoveryFlights.set(config.cache, flights);
6623
+ }
6624
+ for (let attempt = 0; ; attempt++) {
6625
+ const readEpoch = this.discoveryEpochSnapshot(options);
6626
+ const entry = await this.readSharedDiscovery(options);
6627
+ const installed = entry && (!readEpoch || this.discoveryEpochIs(readEpoch)) && this.installSharedCapabilities(entry, key, discoveryContext);
6628
+ if (installed) return installed;
6629
+ const flight = flights.get(key);
6630
+ if (flight && attempt < MAX_SHARED_DISCOVERY_WAITS) {
6631
+ const requestTimeoutMs = resolveRequestTimeoutMs(
6632
+ options?.transport?.requestTimeoutMs ?? this.config.transport?.requestTimeoutMs
6633
+ );
6634
+ await withAbortSignal([options?.signal], requestTimeoutMs, () => flight);
6635
+ continue;
6636
+ }
6637
+ let finish;
6638
+ const mine = new Promise((resolve) => finish = resolve);
6639
+ const ownsFlight = !flights.has(key);
6640
+ if (ownsFlight) flights.set(key, mine);
6641
+ try {
6642
+ return await this.discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext);
6643
+ } finally {
6644
+ if (ownsFlight) {
6645
+ flights.delete(key);
6646
+ }
6647
+ finish();
6648
+ }
6649
+ }
6650
+ }
6651
+ /** Install capabilities from a shared entry; `undefined` when the entry is not usable evidence. */
6652
+ installSharedCapabilities(entry, key, discoveryContext) {
6653
+ const capabilities = entry.capabilities;
6654
+ if (!isCapabilityEvidence(capabilities) || capabilities._synthetic) return void 0;
6655
+ const toolSchemas = entry.toolSchemas;
6656
+ const hasToolEvidence = toolSchemas !== void 0 || Array.isArray(capabilities.discoveredTools);
6657
+ if (!hasToolEvidence) return void 0;
6658
+ if (toolSchemas !== void 0 && !isToolSchemaRecord(toolSchemas)) {
6659
+ return void 0;
6660
+ }
6661
+ let installed;
6662
+ let schemas;
6663
+ try {
6664
+ installed = structuredClone(capabilities);
6665
+ schemas = toolSchemas ? new Map(Object.entries(toolSchemas).map(([tool, properties]) => [tool, structuredClone(properties)])) : void 0;
6666
+ } catch {
6667
+ return void 0;
6668
+ }
6669
+ this.cachedCapabilities = installed;
6670
+ this.cachedToolSchemas = schemas;
6671
+ this.primedCapabilitiesExpiresAt = entry.expiresAt;
6672
+ this.capabilitiesFromSharedCache = true;
6673
+ this.recordSharedEvidence(key);
6674
+ if (discoveryContext) discoveryContext.toolSchemas = this.cachedToolSchemas;
6675
+ this.maybeWarnV2Sunset(installed);
6676
+ return installed;
6677
+ }
6678
+ async discoverCapabilitiesLive(options, usesScopedFetch, discoveryContext) {
6679
+ const startedAtEpoch = this.discoveryEpochSnapshot(options);
6680
+ const superseded = () => startedAtEpoch !== void 0 && !this.discoveryEpochIs(startedAtEpoch);
6479
6681
  const agentInfo = await this.getAgentInfo(options);
6480
6682
  const tools = agentInfo.tools.map((t) => ({
6481
6683
  name: t.name,
@@ -6485,7 +6687,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6485
6687
  agentInfo.tools.filter((t) => t.inputSchema?.properties).map((t) => [t.name, t.inputSchema.properties])
6486
6688
  );
6487
6689
  if (discoveryContext) discoveryContext.toolSchemas = discoveredToolSchemas;
6488
- if (!usesScopedFetch) this.cachedToolSchemas = discoveredToolSchemas;
6690
+ if (!usesScopedFetch && !superseded()) this.cachedToolSchemas = discoveredToolSchemas;
6489
6691
  const advertisesCapabilitiesTool = tools.some((t) => t.name === "get_adcp_capabilities");
6490
6692
  const officialA2ALifecycle = this.normalizedAgent.protocol === "a2a" && tools.some((tool) => ["tasks/get", "get_task_status", "list_tasks"].includes(tool.name));
6491
6693
  const hasCapabilitiesTool = advertisesCapabilitiesTool || officialA2ALifecycle;
@@ -6517,7 +6719,31 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6517
6719
  }
6518
6720
  if (result.success && result.data) {
6519
6721
  const capabilities2 = augmentCapabilitiesFromTools(parseCapabilitiesResponse(result.data), tools);
6520
- if (!usesScopedFetch) this.cachedCapabilities = capabilities2;
6722
+ if (!usesScopedFetch && !superseded()) {
6723
+ this.cachedCapabilities = capabilities2;
6724
+ this.capabilitiesFromSharedCache = false;
6725
+ if (!capabilities2._synthetic && this.config.discoveryCache) {
6726
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6727
+ const stored = await this.writeSharedDiscovery(
6728
+ options,
6729
+ {
6730
+ capabilities: structuredClone(capabilities2),
6731
+ toolSchemas: Object.fromEntries(discoveredToolSchemas)
6732
+ },
6733
+ false,
6734
+ startedAtEpoch
6735
+ );
6736
+ if (superseded()) {
6737
+ if (this.cachedCapabilities === capabilities2) {
6738
+ this.cachedCapabilities = void 0;
6739
+ this.cachedToolSchemas = void 0;
6740
+ this.primedCapabilitiesExpiresAt = void 0;
6741
+ }
6742
+ } else {
6743
+ this.primedCapabilitiesExpiresAt = stored?.expiresAt ?? Date.now() + resolveAgentDiscoveryTtlMs(this.config.discoveryCache.ttlMs);
6744
+ }
6745
+ }
6746
+ }
6521
6747
  this.maybeWarnV2Sunset(capabilities2);
6522
6748
  return capabilities2;
6523
6749
  }
@@ -6536,7 +6762,11 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6536
6762
  majorVersions: parsed.majorVersions.includes(3) ? parsed.majorVersions : [3]
6537
6763
  };
6538
6764
  const capabilities2 = augmentCapabilitiesFromTools(v3Capabilities, tools);
6539
- if (!usesScopedFetch) this.cachedCapabilities = capabilities2;
6765
+ if (!usesScopedFetch && !superseded()) {
6766
+ this.cachedCapabilities = capabilities2;
6767
+ this.capabilitiesFromSharedCache = false;
6768
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6769
+ }
6540
6770
  this.maybeWarnV2Sunset(capabilities2);
6541
6771
  return capabilities2;
6542
6772
  }
@@ -6560,7 +6790,11 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6560
6790
  }
6561
6791
  if (advertisesCapabilitiesTool) {
6562
6792
  const capabilities2 = augmentCapabilitiesFromTools(buildSyntheticV3Capabilities(tools), tools);
6563
- if (!usesScopedFetch) this.cachedCapabilities = capabilities2;
6793
+ if (!usesScopedFetch && !superseded()) {
6794
+ this.cachedCapabilities = capabilities2;
6795
+ this.capabilitiesFromSharedCache = false;
6796
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6797
+ }
6564
6798
  this.maybeWarnV2Sunset(capabilities2);
6565
6799
  return capabilities2;
6566
6800
  }
@@ -6569,7 +6803,11 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6569
6803
  `[AdCP] Agent "${this.agent.id}" detected as v2 (no get_adcp_capabilities tool). Tools: [${tools.map((t) => t.name).join(", ")}]`
6570
6804
  );
6571
6805
  const capabilities = buildSyntheticCapabilities(tools);
6572
- if (!usesScopedFetch) this.cachedCapabilities = capabilities;
6806
+ if (!usesScopedFetch && !superseded()) {
6807
+ this.cachedCapabilities = capabilities;
6808
+ this.capabilitiesFromSharedCache = false;
6809
+ this.recordSharedEvidence(this.sharedDiscoveryKey(options));
6810
+ }
6573
6811
  return capabilities;
6574
6812
  }
6575
6813
  /**
@@ -6640,6 +6878,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6640
6878
  this.cachedCapabilities = void 0;
6641
6879
  this.cachedToolSchemas = void 0;
6642
6880
  this.primedCapabilitiesExpiresAt = void 0;
6881
+ this.capabilitiesFromSharedCache = false;
6643
6882
  return false;
6644
6883
  }
6645
6884
  try {
@@ -6648,6 +6887,8 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6648
6887
  ].map((name) => ({ name }));
6649
6888
  this.cachedCapabilities = augmentCapabilitiesFromTools(structuredClone(capabilities), observedTools);
6650
6889
  this.primedCapabilitiesExpiresAt = expiresAt;
6890
+ this.capabilitiesFromSharedCache = false;
6891
+ this.recordSharedEvidence(this.sharedDiscoveryKey(), true);
6651
6892
  this.cachedToolSchemas = snapshot.toolSchemas ? new Map(
6652
6893
  Object.entries(snapshot.toolSchemas).map(([tool, properties]) => [
6653
6894
  tool,
@@ -6662,6 +6903,396 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6662
6903
  return false;
6663
6904
  }
6664
6905
  }
6906
+ /** Which kind of credential this client presents (never its value), so an unauthenticated client cannot adopt an authenticated one's evidence. */
6907
+ discoveryCredentialKind() {
6908
+ const agent = this.normalizedAgent;
6909
+ if (agent.oauth_client_credentials) return "oauth-client-credentials";
6910
+ if (agent.oauth_tokens) return "oauth";
6911
+ if (agent.auth_token) return "bearer";
6912
+ const headerNames = Object.keys(agent.headers ?? {}).map((name) => name.toLowerCase());
6913
+ return headerNames.includes("authorization") || headerNames.includes("x-adcp-auth") ? "header" : "none";
6914
+ }
6915
+ /** Epoch of this client's evidence for the call's effective shared key; `undefined` without a shared cache. */
6916
+ discoveryEpochSnapshot(options) {
6917
+ const key = this.sharedDiscoveryKey(options);
6918
+ if (!key || !this.config.discoveryCache) return void 0;
6919
+ return { local: this.discoveryEpoch, shared: this.sharedInvalidationEpoch(key), key };
6920
+ }
6921
+ discoveryEpochIs(epoch) {
6922
+ return epoch.local === this.discoveryEpoch && epoch.shared === this.sharedInvalidationEpoch(epoch.key);
6923
+ }
6924
+ /** The latest invalidation that applies to `key`: its own, combined with the cache's floor of forgotten ones. */
6925
+ sharedInvalidationRecord(key) {
6926
+ const cache = this.config.discoveryCache.cache;
6927
+ const record = sharedInvalidations.get(cache)?.get(key);
6928
+ const floor = sharedInvalidationFloors.get(cache);
6929
+ if (!floor) return record;
6930
+ if (!record) return floor;
6931
+ return { epoch: Math.max(record.epoch, floor.epoch), at: Math.max(record.at, floor.at) };
6932
+ }
6933
+ sharedInvalidationEpoch(key) {
6934
+ return this.sharedInvalidationRecord(key)?.epoch ?? 0;
6935
+ }
6936
+ bumpSharedInvalidationEpoch(key) {
6937
+ const cache = this.config.discoveryCache.cache;
6938
+ let records = sharedInvalidations.get(cache);
6939
+ if (!records) {
6940
+ records = /* @__PURE__ */ new Map();
6941
+ sharedInvalidations.set(cache, records);
6942
+ }
6943
+ const now = Date.now();
6944
+ records.delete(key);
6945
+ records.set(key, { epoch: ++sharedInvalidationCounter, at: now });
6946
+ for (const [oldKey, record] of records) {
6947
+ if (records.size <= MAX_SHARED_INVALIDATION_RECORDS && record.at + MAX_AGENT_DISCOVERY_TTL_MS > now) break;
6948
+ const floor = sharedInvalidationFloors.get(cache);
6949
+ sharedInvalidationFloors.set(cache, {
6950
+ epoch: Math.max(record.epoch, floor?.epoch ?? 0),
6951
+ at: Math.max(record.at, floor?.at ?? 0)
6952
+ });
6953
+ records.delete(oldKey);
6954
+ }
6955
+ }
6956
+ /**
6957
+ * Remember which key the local capability evidence belongs to and, unless it
6958
+ * is application-owned (`primeCapabilities`), the shared invalidation epoch it
6959
+ * was obtained under. Application-owned evidence keeps its caller-declared
6960
+ * freshness and is not discarded by another instance's invalidation.
6961
+ */
6962
+ recordSharedEvidence(key, applicationOwned = false) {
6963
+ this.sharedEvidenceKey = key;
6964
+ this.sharedEvidenceEpoch = key && this.config.discoveryCache && !applicationOwned ? this.sharedInvalidationEpoch(key) : void 0;
6965
+ }
6966
+ /** Non-secret fingerprint of the request-signing identity, so keys never mix signing identities. */
6967
+ discoverySigningFingerprint() {
6968
+ const signing = this.normalizedAgent.request_signing;
6969
+ if (!signing) return void 0;
6970
+ const provider = signing.kind === "provider" ? signing.provider : void 0;
6971
+ return {
6972
+ kind: signing.kind ?? "inline",
6973
+ agentUrl: signing.agent_url ?? null,
6974
+ kid: (provider ? provider.keyid : signing.kid) ?? null,
6975
+ alg: (provider ? provider.algorithm : signing.alg) ?? null,
6976
+ alwaysSign: signing.always_sign ?? null,
6977
+ signSupported: signing.sign_supported ?? null
6978
+ };
6979
+ }
6980
+ /**
6981
+ * Shared-discovery key for the effective transport policy of this call, or
6982
+ * `undefined` when sharing is off or the call uses a scoped `trustedFetchFn`
6983
+ * (whose network boundary is per-call, so its evidence is never shared).
6984
+ */
6985
+ sharedDiscoveryKey(options) {
6986
+ const config = this.config.discoveryCache;
6987
+ if (!config) return void 0;
6988
+ const transport = normalizeTransportOptions(options?.transport ?? this.config.transport);
6989
+ if (transport?.trustedFetchFn !== void 0) return void 0;
6990
+ return agentDiscoveryCacheKey({
6991
+ protocol: this.normalizedAgent.protocol,
6992
+ agentUri: this.normalizedAgent.agent_uri,
6993
+ adcpVersion: this.resolvedAdcpVersion,
6994
+ wireAdcpVersion: this.config.wireAdcpVersion,
6995
+ authIdentity: config.authIdentity,
6996
+ credentialKind: this.discoveryCredentialKind(),
6997
+ headers: withAmbientIdentityHeaders(this.normalizedAgent.headers),
6998
+ signing: this.discoverySigningFingerprint(),
6999
+ allowPrivateIp: transport?.allowPrivateIp,
7000
+ maxResponseBytes: transport?.maxResponseBytes
7001
+ });
7002
+ }
7003
+ /**
7004
+ * Run a shared-cache operation under the caller's signal and a fixed backend
7005
+ * deadline, so an unresponsive store can neither make a call uncancellable nor
7006
+ * stall it beyond {@link SHARED_DISCOVERY_CACHE_TIMEOUT_MS}. Rejects on the
7007
+ * caller's abort or when the backend fails or times out; callers decide which
7008
+ * of those is a miss.
7009
+ */
7010
+ boundedCacheOperation(operation, signal) {
7011
+ return withAbortSignal([signal], SHARED_DISCOVERY_CACHE_TIMEOUT_MS, () => Promise.resolve().then(operation));
7012
+ }
7013
+ /**
7014
+ * Run one backend mutation (or read-modify-write) for a shared key strictly
7015
+ * after every earlier mutation of that key in this process has actually
7016
+ * settled. The caller's wait is still bounded: an unresponsive backend makes
7017
+ * the caller see a miss, but later mutations stay queued behind the hung
7018
+ * operation, so an old callback can never land after a newer one.
7019
+ *
7020
+ * The queue is bounded. A `droppable` mutation (a write or cleanup) is
7021
+ * abandoned when its caller stopped waiting before it started, and is refused
7022
+ * when the key's queue is full; refusal is a failed write, never a call
7023
+ * failure. A `delete` is never refused for a known key: while one is queued
7024
+ * and not yet started, a newer invalidation joins it. Only for a new key beyond
7025
+ * the key cap is a delete refused — the invalidation tombstone still hides the
7026
+ * entry from this process's readers.
7027
+ */
7028
+ serializedCacheMutation(key, operation, signal, kind = "droppable") {
7029
+ const cache = this.config.discoveryCache.cache;
7030
+ let chains = sharedMutationChains.get(cache);
7031
+ if (!chains) {
7032
+ chains = /* @__PURE__ */ new Map();
7033
+ sharedMutationChains.set(cache, chains);
7034
+ }
7035
+ let queue = chains.get(key);
7036
+ if (!queue && chains.size >= MAX_SHARED_MUTATION_KEYS) {
7037
+ return Promise.reject(new Error("Shared discovery cache mutation queue is full."));
7038
+ }
7039
+ if (kind === "delete" && queue?.waitingDelete && !queue.waitingDelete.started) {
7040
+ const joined = queue.waitingDelete.promise;
7041
+ return this.boundedCacheOperation(() => joined, signal);
7042
+ }
7043
+ if (kind === "droppable" && queue && queue.pending >= MAX_PENDING_SHARED_MUTATIONS_PER_KEY) {
7044
+ return Promise.reject(new Error("Shared discovery cache mutation queue is full."));
7045
+ }
7046
+ if (!queue) {
7047
+ queue = { tail: Promise.resolve(), pending: 0 };
7048
+ chains.set(key, queue);
7049
+ }
7050
+ const current = queue;
7051
+ const state = { abandoned: false, started: false };
7052
+ const run = current.tail.then(() => {
7053
+ if (kind === "droppable" && state.abandoned) throw new Error("Shared discovery cache mutation abandoned.");
7054
+ state.started = true;
7055
+ if (current.waitingDelete?.promise === run) current.waitingDelete.started = true;
7056
+ return operation();
7057
+ });
7058
+ current.pending++;
7059
+ if (kind === "delete") current.waitingDelete = { promise: run, started: false };
7060
+ const tail = run.then(
7061
+ () => void 0,
7062
+ () => void 0
7063
+ );
7064
+ current.tail = tail;
7065
+ void tail.then(() => {
7066
+ current.pending--;
7067
+ if (current.waitingDelete?.promise === run) current.waitingDelete = void 0;
7068
+ if (current.pending === 0) {
7069
+ if (chains.get(key) === current) chains.delete(key);
7070
+ if (chains.size === 0 && sharedMutationChains.get(cache) === chains) sharedMutationChains.delete(cache);
7071
+ }
7072
+ });
7073
+ return this.boundedCacheOperation(() => run, signal).catch((error) => {
7074
+ state.abandoned = true;
7075
+ throw error;
7076
+ });
7077
+ }
7078
+ /** Is this stored entry structurally sound and not expired (before any caller-TTL cap)? */
7079
+ isLiveSharedEntry(entry, now) {
7080
+ return Number.isFinite(entry.expiresAt) && Number.isFinite(entry.observedAt) && entry.expiresAt >= entry.observedAt && entry.expiresAt > now;
7081
+ }
7082
+ /**
7083
+ * Read a fresh, structurally valid shared entry. A backend failure or
7084
+ * timeout is a miss, never a call failure; the caller's own abort propagates.
7085
+ */
7086
+ async readSharedDiscovery(options) {
7087
+ const key = this.sharedDiscoveryKey(options);
7088
+ if (!key) return void 0;
7089
+ const cache = this.config.discoveryCache.cache;
7090
+ const epoch = this.discoveryEpochSnapshot(options);
7091
+ try {
7092
+ const entry = await this.boundedCacheOperation(() => cache.get(key), options?.signal);
7093
+ if (epoch && !this.discoveryEpochIs(epoch)) return void 0;
7094
+ if (!entry) return void 0;
7095
+ const invalidation = this.sharedInvalidationRecord(key);
7096
+ if (invalidation && entry.observedAt <= invalidation.at) return void 0;
7097
+ const now = Date.now();
7098
+ if (!this.isLiveSharedEntry(entry, now)) {
7099
+ await this.serializedCacheMutation(
7100
+ key,
7101
+ async () => {
7102
+ const current = await cache.get(key);
7103
+ if (current && !this.isLiveSharedEntry(current, Date.now())) await cache.delete(key);
7104
+ },
7105
+ options?.signal
7106
+ );
7107
+ return void 0;
7108
+ }
7109
+ if (entry.observedAt > now + SHARED_DISCOVERY_CLOCK_SKEW_MS) return void 0;
7110
+ const callerExpiresAt = entry.observedAt + resolveAgentDiscoveryTtlMs(this.config.discoveryCache.ttlMs);
7111
+ if (!(callerExpiresAt > now)) return void 0;
7112
+ return callerExpiresAt < entry.expiresAt ? { ...entry, expiresAt: callerExpiresAt } : entry;
7113
+ } catch {
7114
+ throwIfAborted(options?.signal);
7115
+ return void 0;
7116
+ }
7117
+ }
7118
+ /**
7119
+ * Merge fresh evidence into the shared entry; the merged entry never outlives
7120
+ * its oldest part. Returns the stored entry, or `undefined` when it was not
7121
+ * stored: automatic writes
7122
+ * ignore a `false` (a failed write only costs a later rediscovery), while
7123
+ * {@link primeDiscoveryCache} reports it.
7124
+ */
7125
+ async writeSharedDiscovery(options, patch, replace = false, epoch = this.discoveryEpochSnapshot(options)) {
7126
+ const key = this.sharedDiscoveryKey(options);
7127
+ if (!key) return void 0;
7128
+ if (patch.endpoint && !this.isDiscoverableEndpoint(patch.endpoint.agentUri)) {
7129
+ patch = { capabilities: patch.capabilities, toolSchemas: patch.toolSchemas };
7130
+ if (!patch.capabilities && !patch.toolSchemas) return void 0;
7131
+ }
7132
+ const config = this.config.discoveryCache;
7133
+ const stale = () => epoch !== void 0 && !this.discoveryEpochIs(epoch);
7134
+ try {
7135
+ return await this.serializedCacheMutation(
7136
+ key,
7137
+ async () => {
7138
+ if (stale()) return void 0;
7139
+ const invalidation = this.sharedInvalidationRecord(key);
7140
+ const now = Math.max(Date.now(), (invalidation?.at ?? 0) + 1);
7141
+ let existing;
7142
+ if (!replace) {
7143
+ const raw = await config.cache.get(key);
7144
+ if (raw && this.isLiveSharedEntry(raw, now) && raw.observedAt <= now + SHARED_DISCOVERY_CLOCK_SKEW_MS && !(invalidation && raw.observedAt <= invalidation.at)) {
7145
+ const endpointObservedAt = raw.endpoint?.observedAt ?? raw.observedAt;
7146
+ existing = { ...raw };
7147
+ if (existing.endpoint && (!this.isDiscoverableEndpoint(existing.endpoint.agentUri) || invalidation && endpointObservedAt <= invalidation.at))
7148
+ delete existing.endpoint;
7149
+ }
7150
+ if (stale()) return void 0;
7151
+ }
7152
+ const restartsClock = !existing || patch.capabilities !== void 0;
7153
+ const ttl = resolveAgentDiscoveryTtlMs(config.ttlMs);
7154
+ const entry = {
7155
+ ...existing?.capabilities && { capabilities: existing.capabilities },
7156
+ ...existing?.toolSchemas && { toolSchemas: existing.toolSchemas },
7157
+ ...existing?.endpoint && { endpoint: existing.endpoint },
7158
+ ...patch.endpoint && { endpoint: { ...patch.endpoint, observedAt: now } },
7159
+ ...patch.capabilities && { capabilities: patch.capabilities },
7160
+ ...patch.toolSchemas && { toolSchemas: patch.toolSchemas },
7161
+ observedAt: restartsClock || !existing ? now : Math.min(existing.observedAt, now),
7162
+ expiresAt: restartsClock || !existing ? now + ttl : Math.min(existing.expiresAt, now + ttl)
7163
+ };
7164
+ await config.cache.set(key, structuredClone(entry));
7165
+ return stale() ? void 0 : entry;
7166
+ },
7167
+ options?.signal
7168
+ );
7169
+ } catch {
7170
+ return void 0;
7171
+ }
7172
+ }
7173
+ /** Is `agentUri` a credential-free URL on the configured agent's origin (all discovery ever tries)? */
7174
+ isDiscoverableEndpoint(agentUri) {
7175
+ if (typeof agentUri !== "string") return false;
7176
+ try {
7177
+ const candidate = new URL(agentUri);
7178
+ return candidate.username === "" && candidate.password === "" && candidate.search === "" && candidate.hash === "" && candidate.origin === new URL(this.normalizedAgent.agent_uri).origin;
7179
+ } catch {
7180
+ return false;
7181
+ }
7182
+ }
7183
+ /** Adopt a previously discovered MCP endpoint instead of re-probing the agent. */
7184
+ async adoptSharedEndpoint(options) {
7185
+ const readEpoch = this.discoveryEpochSnapshot(options);
7186
+ const entry = await this.readSharedDiscovery(options);
7187
+ if (readEpoch && !this.discoveryEpochIs(readEpoch)) return;
7188
+ const endpoint = entry?.endpoint;
7189
+ if (!entry || !endpoint || !this.isDiscoverableEndpoint(endpoint.agentUri)) return;
7190
+ const observedAt = Number.isFinite(endpoint.observedAt) ? endpoint.observedAt : entry.observedAt;
7191
+ const sharedKey = this.sharedDiscoveryKey(options);
7192
+ const tombstone = sharedKey ? this.sharedInvalidationRecord(sharedKey) : void 0;
7193
+ if (tombstone && observedAt <= tombstone.at) return;
7194
+ const expiresAt = Math.min(
7195
+ entry.expiresAt,
7196
+ observedAt + resolveAgentDiscoveryTtlMs(this.config.discoveryCache.ttlMs)
7197
+ );
7198
+ if (!(expiresAt > Date.now()) || observedAt > Date.now() + SHARED_DISCOVERY_CLOCK_SKEW_MS) return;
7199
+ this.discoveredEndpoint = endpoint.agentUri;
7200
+ this.discoveredEndpointKey = this.sharedDiscoveryKey(options);
7201
+ this.discoveredEndpointEpoch = this.discoveredEndpointKey ? this.sharedInvalidationEpoch(this.discoveredEndpointKey) : void 0;
7202
+ this.discoveredEndpointExpiresAt = expiresAt;
7203
+ this.canonicalBaseUrl = this.computeBaseUrl(endpoint.agentUri);
7204
+ if (endpoint.mcpEra === "legacy" || endpoint.mcpEra === "modern") {
7205
+ this.discoveredMcpEra = endpoint.mcpEra;
7206
+ this.discoveredMcpEraAt = observedAt;
7207
+ }
7208
+ }
7209
+ /**
7210
+ * Drop discovery evidence for this client: its in-memory capability and
7211
+ * tool-schema caches, the discovered endpoint, and the shared
7212
+ * `discoveryCache` entry (when configured). The next call rediscovers.
7213
+ *
7214
+ * Call this when you learn the seller changed (a deploy, a version bump, a
7215
+ * capability-dependent error you observed). The SDK also invalidates by
7216
+ * itself when a feature check fails against shared evidence, and when a
7217
+ * seller answers with a version or capability error.
7218
+ */
7219
+ async invalidateDiscoveryCache(options) {
7220
+ this.cachedCapabilities = void 0;
7221
+ this.cachedToolSchemas = void 0;
7222
+ this.primedCapabilitiesExpiresAt = void 0;
7223
+ this.capabilitiesFromSharedCache = false;
7224
+ this.capabilityEvidenceScopeKey = randomUUID();
7225
+ this.discoveryEpoch++;
7226
+ if (this.normalizedAgent._needsDiscovery) {
7227
+ this.discoveredEndpoint = void 0;
7228
+ this.discoveredAgent = void 0;
7229
+ this.discoveredMcpEra = void 0;
7230
+ this.discoveredEndpointKey = void 0;
7231
+ this.discoveredEndpointEpoch = void 0;
7232
+ this.discoveredEndpointExpiresAt = void 0;
7233
+ }
7234
+ const key = this.sharedDiscoveryKey(options);
7235
+ if (!key) return;
7236
+ this.bumpSharedInvalidationEpoch(key);
7237
+ try {
7238
+ const cache = this.config.discoveryCache.cache;
7239
+ await this.serializedCacheMutation(key, () => cache.delete(key), void 0, "delete");
7240
+ } catch {
7241
+ }
7242
+ }
7243
+ /**
7244
+ * Pre-seed the shared `discoveryCache` with capability evidence the caller
7245
+ * already holds (for example from a registry sync), so no client sharing
7246
+ * this identity probes the seller within the TTL. Unlike
7247
+ * {@link primeCapabilities} the seeded evidence outlives this instance.
7248
+ *
7249
+ * Returns `true` only once the backing cache has accepted the entry. Returns
7250
+ * `false` — having seeded nothing — when sharing is not configured, the
7251
+ * client uses a scoped `trustedFetchFn`, or the evidence is synthetic,
7252
+ * malformed or not cloneable. When the cache rejects the write or does not
7253
+ * answer within the backend deadline, the result is also `false`, but the
7254
+ * outcome is indeterminate: a write already handed to the backend may still
7255
+ * land, whereas one still queued behind earlier operations is dropped. Treat
7256
+ * `false` as "not confirmed", and seed again (or invalidate) rather than
7257
+ * assuming the cache is empty. Seed `endpoint` as well
7258
+ * to skip the endpoint probe on the first call; without it that probe still
7259
+ * runs once per cold client.
7260
+ */
7261
+ async primeDiscoveryCache(seed) {
7262
+ const key = this.sharedDiscoveryKey();
7263
+ const capabilities = seed?.capabilities;
7264
+ if (!key || !isCapabilityEvidence(capabilities) || capabilities._synthetic) return false;
7265
+ const toolSchemas = seed.toolSchemas;
7266
+ if (toolSchemas !== void 0 && !isToolSchemaRecord(toolSchemas)) {
7267
+ return false;
7268
+ }
7269
+ if (toolSchemas === void 0 && !Array.isArray(capabilities.discoveredTools)) return false;
7270
+ if (seed.endpoint !== void 0 && !this.isDiscoverableEndpoint(seed.endpoint.agentUri)) return false;
7271
+ try {
7272
+ const stored = await this.writeSharedDiscovery(
7273
+ void 0,
7274
+ {
7275
+ capabilities: structuredClone(capabilities),
7276
+ ...toolSchemas && { toolSchemas: structuredClone(toolSchemas) },
7277
+ ...seed.endpoint && {
7278
+ endpoint: {
7279
+ agentUri: seed.endpoint.agentUri,
7280
+ ...seed.endpoint.mcpEra && { mcpEra: seed.endpoint.mcpEra }
7281
+ }
7282
+ }
7283
+ },
7284
+ true
7285
+ );
7286
+ return stored !== void 0;
7287
+ } catch {
7288
+ return false;
7289
+ }
7290
+ }
7291
+ /** Drop shared evidence when a seller answers with an error that says our cached view of it is stale. */
7292
+ async invalidateDiscoveryOnCapabilityError(code, options) {
7293
+ if (!this.config.discoveryCache || !code || !CAPABILITY_STALE_ERROR_CODES.has(code)) return;
7294
+ await this.invalidateDiscoveryCache(options);
7295
+ }
6665
7296
  /**
6666
7297
  * Emit a one-time warning when the agent reports v2 capabilities.
6667
7298
  *
@@ -6810,8 +7441,21 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6810
7441
  * with an actionable error message.
6811
7442
  */
6812
7443
  async require(...features) {
6813
- const capabilities = await this.getCapabilities();
6814
- const missing = features.filter((f) => !resolveFeature(capabilities, f));
7444
+ await this.assertFeaturesSupported(features);
7445
+ }
7446
+ /**
7447
+ * Throw {@link FeatureUnsupportedError} when a feature is missing, deciding on
7448
+ * live capabilities when the refusal would rest on shared evidence that may
7449
+ * predate a seller upgrade.
7450
+ */
7451
+ async assertFeaturesSupported(requiredFeatures, options) {
7452
+ let capabilities = await this.getCapabilities(options);
7453
+ let missing = requiredFeatures.filter((f) => !resolveFeature(capabilities, f));
7454
+ if (missing.length > 0 && this.capabilitiesFromSharedCache) {
7455
+ await this.invalidateDiscoveryCache(options);
7456
+ capabilities = await this.getCapabilities(options);
7457
+ missing = requiredFeatures.filter((f) => !resolveFeature(capabilities, f));
7458
+ }
6815
7459
  if (missing.length > 0) {
6816
7460
  throw new FeatureUnsupportedError(missing, listDeclaredFeatures(capabilities), this.agent.agent_uri);
6817
7461
  }
@@ -6824,7 +7468,10 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6824
7468
  this.cachedCapabilities = void 0;
6825
7469
  this.cachedToolSchemas = void 0;
6826
7470
  this.primedCapabilitiesExpiresAt = void 0;
7471
+ this.capabilitiesFromSharedCache = false;
6827
7472
  this.capabilityEvidenceScopeKey = randomUUID();
7473
+ this.discoveryEpoch++;
7474
+ if (this.config.discoveryCache) await this.invalidateDiscoveryCache();
6828
7475
  return this.getCapabilities();
6829
7476
  }
6830
7477
  /**
@@ -6837,11 +7484,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
6837
7484
  if (this.config.validateFeatures === false) return;
6838
7485
  const requiredFeatures = TASK_FEATURE_MAP[taskName];
6839
7486
  if (!requiredFeatures || requiredFeatures.length === 0) return;
6840
- const capabilities = await this.getCapabilities(options);
6841
- const missing = requiredFeatures.filter((f) => !resolveFeature(capabilities, f));
6842
- if (missing.length > 0) {
6843
- throw new FeatureUnsupportedError(missing, listDeclaredFeatures(capabilities), this.agent.agent_uri);
6844
- }
7487
+ await this.assertFeaturesSupported(requiredFeatures, options);
6845
7488
  }
6846
7489
  /**
6847
7490
  * Fail version-incompatible request shapes before schema validation.