@adcp/sdk 15.4.0 → 15.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.
Files changed (115) hide show
  1. package/dist/lib/advanced.d.mts +2 -2
  2. package/dist/lib/advanced.d.ts +2 -2
  3. package/dist/lib/advanced.js +8 -2
  4. package/dist/lib/advanced.mjs +8 -2
  5. package/dist/lib/core/AgentClient.d.mts +4 -0
  6. package/dist/lib/core/AgentClient.d.ts +4 -0
  7. package/dist/lib/core/AgentClient.js +8 -0
  8. package/dist/lib/core/AgentClient.mjs +8 -0
  9. package/dist/lib/core/SingleAgentClient.d.mts +154 -0
  10. package/dist/lib/core/SingleAgentClient.d.ts +154 -0
  11. package/dist/lib/core/SingleAgentClient.js +661 -23
  12. package/dist/lib/core/SingleAgentClient.mjs +666 -23
  13. package/dist/lib/core/agent-discovery-cache.d.mts +103 -0
  14. package/dist/lib/core/agent-discovery-cache.d.ts +103 -0
  15. package/dist/lib/core/agent-discovery-cache.js +121 -0
  16. package/dist/lib/core/agent-discovery-cache.mjs +92 -0
  17. package/dist/lib/index.d.mts +5 -3
  18. package/dist/lib/index.d.ts +5 -3
  19. package/dist/lib/index.js +7 -0
  20. package/dist/lib/index.mjs +6 -0
  21. package/dist/lib/protocols/index.d.mts +2 -2
  22. package/dist/lib/protocols/index.d.ts +2 -2
  23. package/dist/lib/protocols/index.js +1 -1
  24. package/dist/lib/protocols/index.mjs +1 -1
  25. package/dist/lib/protocols/mcp-call-context.d.mts +109 -0
  26. package/dist/lib/protocols/mcp-call-context.d.ts +109 -0
  27. package/dist/lib/protocols/mcp-call-context.js +221 -0
  28. package/dist/lib/protocols/mcp-call-context.mjs +186 -0
  29. package/dist/lib/protocols/mcp-modern.d.mts +9 -1
  30. package/dist/lib/protocols/mcp-modern.d.ts +9 -1
  31. package/dist/lib/protocols/mcp-modern.js +180 -104
  32. package/dist/lib/protocols/mcp-modern.mjs +193 -104
  33. package/dist/lib/protocols/mcp-scope.d.mts +15 -0
  34. package/dist/lib/protocols/mcp-scope.d.ts +15 -0
  35. package/dist/lib/protocols/mcp.d.mts +4 -2
  36. package/dist/lib/protocols/mcp.d.ts +4 -2
  37. package/dist/lib/protocols/mcp.js +178 -154
  38. package/dist/lib/protocols/mcp.mjs +189 -153
  39. package/dist/lib/protocols/transportDiagnostics.d.mts +33 -1
  40. package/dist/lib/protocols/transportDiagnostics.d.ts +33 -1
  41. package/dist/lib/protocols/transportDiagnostics.js +128 -12
  42. package/dist/lib/protocols/transportDiagnostics.mjs +128 -12
  43. package/dist/lib/reporting/gcs/index.d.mts +2 -0
  44. package/dist/lib/reporting/gcs/index.d.ts +2 -0
  45. package/dist/lib/reporting/gcs/index.js +3 -0
  46. package/dist/lib/reporting/gcs/index.mjs +2 -0
  47. package/dist/lib/reporting/gcs/row-provider.d.mts +32 -0
  48. package/dist/lib/reporting/gcs/row-provider.d.ts +32 -0
  49. package/dist/lib/reporting/gcs/row-provider.js +205 -0
  50. package/dist/lib/reporting/gcs/row-provider.mjs +188 -0
  51. package/dist/lib/reporting/ledger/change-feed.d.mts +41 -0
  52. package/dist/lib/reporting/ledger/change-feed.d.ts +41 -0
  53. package/dist/lib/reporting/ledger/change-feed.js +101 -0
  54. package/dist/lib/reporting/ledger/change-feed.mjs +74 -0
  55. package/dist/lib/reporting/ledger/handler.js +22 -8
  56. package/dist/lib/reporting/ledger/handler.mjs +22 -8
  57. package/dist/lib/reporting/ledger/index.d.mts +4 -0
  58. package/dist/lib/reporting/ledger/index.d.ts +4 -0
  59. package/dist/lib/reporting/ledger/index.js +9 -1
  60. package/dist/lib/reporting/ledger/index.mjs +4 -0
  61. package/dist/lib/reporting/ledger/managed-postgres.d.mts +2 -0
  62. package/dist/lib/reporting/ledger/managed-postgres.d.ts +2 -0
  63. package/dist/lib/reporting/ledger/managed-postgres.js +21 -1
  64. package/dist/lib/reporting/ledger/managed-postgres.mjs +21 -1
  65. package/dist/lib/reporting/ledger/managed.d.mts +9 -0
  66. package/dist/lib/reporting/ledger/managed.d.ts +9 -0
  67. package/dist/lib/reporting/ledger/managed.js +18 -0
  68. package/dist/lib/reporting/ledger/managed.mjs +18 -0
  69. package/dist/lib/reporting/ledger/postgres.d.mts +175 -0
  70. package/dist/lib/reporting/ledger/postgres.d.ts +175 -0
  71. package/dist/lib/reporting/ledger/postgres.js +851 -26
  72. package/dist/lib/reporting/ledger/postgres.mjs +859 -26
  73. package/dist/lib/reporting/ledger/producer.js +10 -1
  74. package/dist/lib/reporting/ledger/producer.mjs +10 -1
  75. package/dist/lib/reporting/ledger/row-storage-errors.d.mts +7 -0
  76. package/dist/lib/reporting/ledger/row-storage-errors.d.ts +7 -0
  77. package/dist/lib/reporting/ledger/row-storage-errors.js +41 -0
  78. package/dist/lib/reporting/ledger/row-storage-errors.mjs +16 -0
  79. package/dist/lib/reporting/ledger/row-storage-migration.d.mts +41 -0
  80. package/dist/lib/reporting/ledger/row-storage-migration.d.ts +41 -0
  81. package/dist/lib/reporting/ledger/row-storage-migration.js +234 -0
  82. package/dist/lib/reporting/ledger/row-storage-migration.mjs +208 -0
  83. package/dist/lib/reporting/ledger/row-storage-object.d.mts +112 -0
  84. package/dist/lib/reporting/ledger/row-storage-object.d.ts +112 -0
  85. package/dist/lib/reporting/ledger/row-storage-object.js +281 -0
  86. package/dist/lib/reporting/ledger/row-storage-object.mjs +239 -0
  87. package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +51 -0
  88. package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +51 -0
  89. package/dist/lib/reporting/ledger/row-storage-provider-io.js +152 -0
  90. package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +119 -0
  91. package/dist/lib/reporting/ledger/row-storage.d.mts +224 -0
  92. package/dist/lib/reporting/ledger/row-storage.d.ts +224 -0
  93. package/dist/lib/reporting/ledger/row-storage.js +692 -0
  94. package/dist/lib/reporting/ledger/row-storage.mjs +680 -0
  95. package/dist/lib/reporting/ledger/types.d.mts +8 -0
  96. package/dist/lib/reporting/ledger/types.d.ts +8 -0
  97. package/dist/lib/reporting/service/index.d.mts +41 -1
  98. package/dist/lib/reporting/service/index.d.ts +41 -1
  99. package/dist/lib/reporting/service/index.js +68 -1
  100. package/dist/lib/reporting/service/index.mjs +70 -1
  101. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  102. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  103. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  104. package/dist/lib/version.d.mts +3 -3
  105. package/dist/lib/version.d.ts +3 -3
  106. package/dist/lib/version.js +3 -3
  107. package/dist/lib/version.mjs +3 -3
  108. package/docs/README.md +1 -1
  109. package/docs/TYPE-SUMMARY.md +35 -2
  110. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +142 -0
  111. package/docs/guides/EXISTING-PLATFORM.md +43 -12
  112. package/docs/guides/REPORTING-LEDGER.md +209 -0
  113. package/docs/llms.txt +4 -2
  114. package/docs/migration-14.x-rc-worksheet.md +4 -4
  115. package/package.json +1 -1
@@ -20,6 +20,20 @@ import {
20
20
  registerMCPConnectionScopePending
21
21
  } from "./mcp-scope.mjs";
22
22
  import { terminateSessionBestEffort } from "./session-termination.mjs";
23
+ import {
24
+ applyMCPCallHeaders,
25
+ isCorrelationHeader,
26
+ withAmbientIdentityHeaders,
27
+ closeWhenIdle,
28
+ linkedCallSignal,
29
+ joinPendingConnection,
30
+ RETRY_CONNECT,
31
+ requestTimeoutFor,
32
+ runCallPhase,
33
+ runWithMCPCallContext,
34
+ splitConnectionHeaders,
35
+ trackConnectionUse
36
+ } from "./mcp-call-context.mjs";
23
37
  import { buildAgentSigningFetch, signingContextStorage } from "../signing/client.mjs";
24
38
  import {
25
39
  isAbortOrTimeoutError,
@@ -44,10 +58,8 @@ const LEGACY_CLASSIFICATION_TTL_MS = 5 * 60 * 1e3;
44
58
  const MODERN_DISCOVERY_TTL_MS = 5 * 60 * 1e3;
45
59
  const modernOAuthProviderIds = /* @__PURE__ */ new WeakMap();
46
60
  const modernFetchFnIds = /* @__PURE__ */ new WeakMap();
47
- const modernSignalIds = /* @__PURE__ */ new WeakMap();
48
61
  let nextModernOAuthProviderId = 0;
49
62
  let nextModernFetchFnId = 0;
50
- let nextModernSignalId = 0;
51
63
  let connectionGeneration = 0;
52
64
  async function closeModernClient(client, terminateSession = true) {
53
65
  const transport = modernTransports.get(client);
@@ -67,7 +79,7 @@ function buildAuthHeaders(authToken, customHeaders, authProvider) {
67
79
  return key !== "authorization" && key !== "x-adcp-auth";
68
80
  })
69
81
  ) : customHeaders;
70
- return createMCPRequestHeaders(filteredHeaders, authProvider ? void 0 : authToken);
82
+ return withAmbientIdentityHeaders(createMCPRequestHeaders(filteredHeaders, authProvider ? void 0 : authToken));
71
83
  }
72
84
  function oauthProviderCacheKey(provider) {
73
85
  if (!provider) return void 0;
@@ -87,33 +99,26 @@ function fetchFnCacheKey(fetchFn) {
87
99
  }
88
100
  return key;
89
101
  }
90
- function signalCacheKey(signal) {
91
- if (!signal) return void 0;
92
- let key = modernSignalIds.get(signal);
93
- if (!key) {
94
- key = `signal:${++nextModernSignalId}`;
95
- modernSignalIds.set(signal, key);
96
- }
97
- return key;
98
- }
99
- function connectionCacheKey(agentUrl, headers, signingCacheKey, authProvider, fetchFn, signal, requestTimeoutMs, handleLegacy, allowPrivateIp) {
100
- const normalizedHeaders = Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]).sort(([left], [right]) => left.localeCompare(right));
102
+ function connectionCacheKey(agentUrl, headers, signingCacheKey, authProvider, fetchFn, handleLegacy, allowPrivateIp) {
103
+ const normalizedHeaders = Object.entries(splitConnectionHeaders(headers).identity).map(([key, value]) => [key.toLowerCase(), value]).sort(([left], [right]) => left.localeCompare(right));
101
104
  const parts = [agentUrl, `headers:${cacheDisambiguator(JSON.stringify(normalizedHeaders))}`];
102
105
  if (signingCacheKey) parts.push(signingCacheKey);
103
106
  const providerKey = oauthProviderCacheKey(authProvider);
104
107
  if (providerKey) parts.push(providerKey);
105
108
  const fetchKey = fetchFnCacheKey(fetchFn);
106
109
  if (fetchKey) parts.push(fetchKey);
107
- const signalKey = signalCacheKey(signal);
108
- if (signalKey) parts.push(signalKey);
109
- if (requestTimeoutMs !== void 0) parts.push(`timeout:${requestTimeoutMs}`);
110
110
  if (handleLegacy !== void 0) parts.push(`handle-legacy:${handleLegacy}`);
111
111
  if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
112
112
  const scopeKey = currentMCPConnectionScopeKey();
113
113
  if (scopeKey) parts.push(scopeKey);
114
114
  return parts.join("::");
115
115
  }
116
- function isKnownLegacy(cacheKey) {
116
+ function classificationKey(cacheKey) {
117
+ const marker = cacheKey.lastIndexOf("::scope:");
118
+ return marker >= 0 ? cacheKey.slice(0, marker) : cacheKey;
119
+ }
120
+ function isKnownLegacy(connectionKey) {
121
+ const cacheKey = classificationKey(connectionKey);
117
122
  const classifiedAt = knownLegacyConnections.get(cacheKey);
118
123
  if (classifiedAt === void 0) return false;
119
124
  if (Date.now() - classifiedAt > LEGACY_CLASSIFICATION_TTL_MS) {
@@ -124,7 +129,9 @@ function isKnownLegacy(cacheKey) {
124
129
  knownLegacyConnections.set(cacheKey, classifiedAt);
125
130
  return true;
126
131
  }
127
- function markKnownLegacy(cacheKey) {
132
+ function markKnownLegacy(connectionKey) {
133
+ const cacheKey = classificationKey(connectionKey);
134
+ modernDiscoveries.delete(connectionKey);
128
135
  modernDiscoveries.delete(cacheKey);
129
136
  knownLegacyConnections.delete(cacheKey);
130
137
  knownLegacyConnections.set(cacheKey, Date.now());
@@ -146,6 +153,7 @@ function getCachedModernDiscovery(cacheKey) {
146
153
  return { kind: "modern", discover: cached.discover };
147
154
  }
148
155
  function cacheModernDiscovery(cacheKey, discover) {
156
+ knownLegacyConnections.delete(classificationKey(cacheKey));
149
157
  modernDiscoveries.delete(cacheKey);
150
158
  modernDiscoveries.set(cacheKey, {
151
159
  discover,
@@ -172,8 +180,12 @@ function withPerRequestTraceHeaders(fetchImpl) {
172
180
  return (input, init) => {
173
181
  const headers = new Headers(input instanceof Request ? input.headers : void 0);
174
182
  new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
175
- for (const [key, value] of Object.entries(injectTraceHeaders())) headers.set(key, value);
176
- return fetchImpl(input, { ...init, headers });
183
+ applyMCPCallHeaders(headers);
184
+ if (headers.has("traceparent")) return fetchImpl(input, { ...init, headers, ...linkedCallSignal(init) });
185
+ for (const [key, value] of Object.entries(injectTraceHeaders())) {
186
+ if (isCorrelationHeader(key) && !headers.has(key)) headers.set(key, value);
187
+ }
188
+ return fetchImpl(input, { ...init, headers, ...linkedCallSignal(init) });
177
189
  };
178
190
  }
179
191
  function getCachedConnection(cacheKey) {
@@ -183,7 +195,7 @@ function getCachedConnection(cacheKey) {
183
195
  if (legacyExpiry !== void 0 && legacyExpiry <= Date.now()) {
184
196
  modernConnections.delete(cacheKey);
185
197
  legacyConnectionExpiresAt.delete(cacheKey);
186
- void closeModernClient(client).catch(() => {
198
+ void closeWhenIdle(client, () => closeModernClient(client)).catch(() => {
187
199
  });
188
200
  return void 0;
189
201
  }
@@ -199,7 +211,7 @@ function evictLeastRecentlyUsed() {
199
211
  const client = modernConnections.get(oldestKey);
200
212
  modernConnections.delete(oldestKey);
201
213
  legacyConnectionExpiresAt.delete(oldestKey);
202
- if (client) void closeModernClient(client).catch(() => {
214
+ if (client) void closeWhenIdle(client, () => closeModernClient(client)).catch(() => {
203
215
  });
204
216
  }
205
217
  function isNegotiationAuthFailure(error) {
@@ -214,13 +226,24 @@ function presentedStaticCredential(authHeaders, authProvider) {
214
226
  }
215
227
  async function createNegotiatedClient(cacheKey, options, authHeaders, useCachedDiscovery = true, skipProbe = false) {
216
228
  const generation = connectionGeneration;
217
- const requestTimeoutMs = resolveRequestTimeoutMs(options.requestTimeoutMs);
218
229
  const clientRequestTimeoutMs = resolveClientRequestTimeoutMs(options.requestTimeoutMs);
219
230
  const rawNetworkFetch = createAgentTransportFetch(options.agentUrl, {
220
231
  trustedFetchFn: options.fetchFn,
221
232
  allowPrivateIp: options.allowPrivateIp
222
233
  });
223
- const networkFetch = (input, init) => withAbortSignal([init?.signal], requestTimeoutMs, (signal) => rawNetworkFetch(input, { ...init, signal }));
234
+ const networkFetch = (input, init) => (
235
+ // `init.signal` already carries the active call's cancellation (linked at the
236
+ // outermost transport wrapper), including on legacy-era sessions where the
237
+ // v2 client cancels with a notification and leaves the HTTP stream open.
238
+ withAbortSignal(
239
+ [init?.signal],
240
+ requestTimeoutFor(init),
241
+ (deadline) => rawNetworkFetch(input, {
242
+ ...init,
243
+ signal: init?.signal && deadline ? AbortSignal.any([init.signal, deadline]) : deadline ?? init?.signal
244
+ })
245
+ )
246
+ );
224
247
  const diagnosticFetch = wrapFetchWithTransportDiagnostics(wrapFetchWithSizeLimit(networkFetch));
225
248
  const signedFetch = options.signingContext ? buildAgentSigningFetch({
226
249
  upstream: wrapFetchWithSignedRequestRejection(diagnosticFetch, options.agentUrl),
@@ -229,7 +252,7 @@ async function createNegotiatedClient(cacheKey, options, authHeaders, useCachedD
229
252
  adcpVersion: options.signingContext.adcpVersion
230
253
  }) : diagnosticFetch;
231
254
  const transport = new StreamableHTTPClientTransport(new URL(options.agentUrl), {
232
- requestInit: { headers: authHeaders, redirect: "manual" },
255
+ requestInit: { headers: splitConnectionHeaders(authHeaders).identity, redirect: "manual" },
233
256
  fetch: wrapFetchWithCapture(withPerRequestTraceHeaders(signedFetch)),
234
257
  ...options.authProvider && {
235
258
  authProvider: options.authProvider
@@ -277,10 +300,19 @@ async function createNegotiatedClient(cacheKey, options, authHeaders, useCachedD
277
300
  }
278
301
  }
279
302
  async function getOrCreateModernConnection(cacheKey, options, authHeaders) {
280
- const cached = getCachedConnection(cacheKey);
281
- if (cached) return cached;
282
- const pending = pendingModernConnections.get(cacheKey);
283
- if (pending) return pending;
303
+ for (let attempt = 0; ; attempt++) {
304
+ const cached = getCachedConnection(cacheKey);
305
+ if (cached) return cached;
306
+ const pending = pendingModernConnections.get(cacheKey);
307
+ if (pending) {
308
+ const joined = await joinPendingConnection(pending, options, attempt);
309
+ if (joined === RETRY_CONNECT) continue;
310
+ return joined;
311
+ }
312
+ return createSharedModernConnection(cacheKey, options, authHeaders);
313
+ }
314
+ }
315
+ function createSharedModernConnection(cacheKey, options, authHeaders) {
284
316
  const generation = connectionGeneration;
285
317
  const promise = createNegotiatedClient(cacheKey, options, authHeaders).then(async (client) => {
286
318
  if ((client.getProtocolEra() === "modern" || options.handleLegacy === true) && generation === connectionGeneration) {
@@ -311,6 +343,16 @@ async function getOrCreateModernConnection(cacheKey, options, authHeaders) {
311
343
  registerMCPConnectionScopePending(promise);
312
344
  return promise;
313
345
  }
346
+ async function retireFailedConnection(cacheKey, client, error) {
347
+ if (isAbortOrTimeoutError(error)) return;
348
+ if (modernConnections.get(cacheKey) === client) {
349
+ modernConnections.delete(cacheKey);
350
+ legacyConnectionExpiresAt.delete(cacheKey);
351
+ }
352
+ modernDiscoveries.delete(cacheKey);
353
+ await closeWhenIdle(client, () => closeModernClient(client, true)).catch(() => {
354
+ });
355
+ }
314
356
  async function callOnModernClient(client, toolName, args, signal, requestTimeoutMs) {
315
357
  const resolvedRequestTimeoutMs = resolveClientRequestTimeoutMs(requestTimeoutMs);
316
358
  return await client.callTool(
@@ -329,14 +371,11 @@ async function attemptModernCall(options, toolName, args) {
329
371
  options.signingContext?.cacheKey,
330
372
  options.authProvider,
331
373
  options.fetchFn,
332
- options.signal,
333
- options.requestTimeoutMs,
334
374
  options.handleLegacy,
335
375
  options.allowPrivateIp
336
376
  );
337
377
  if (isKnownLegacy(cacheKey)) return { handled: false };
338
- const guardedConnection = options.signal !== void 0 || options.requestTimeoutMs !== void 0 || options.fetchFn !== void 0;
339
- const oneShot = guardedConnection && currentMCPConnectionScopeKey() === void 0;
378
+ const oneShot = options.fetchFn !== void 0 && currentMCPConnectionScopeKey() === void 0;
340
379
  let client;
341
380
  let callSucceeded = false;
342
381
  try {
@@ -390,16 +429,21 @@ async function attemptModernCall(options, toolName, args) {
390
429
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
391
430
  });
392
431
  try {
393
- const response = await callOnModernClient(client, toolName, args, options.signal, options.requestTimeoutMs);
432
+ const response = await trackConnectionUse(
433
+ client,
434
+ () => runCallPhase(
435
+ options.signal,
436
+ () => callOnModernClient(client, toolName, args, options.signal, options.requestTimeoutMs)
437
+ )
438
+ );
394
439
  callSucceeded = true;
395
440
  return { handled: true, response };
396
441
  } catch (error) {
397
- modernConnections.delete(cacheKey);
398
- legacyConnectionExpiresAt.delete(cacheKey);
399
- modernDiscoveries.delete(cacheKey);
400
- try {
401
- await closeModernClient(client, !isAbortOrTimeoutError(error));
402
- } catch {
442
+ if (oneShot) {
443
+ await closeModernClient(client, !isAbortOrTimeoutError(error)).catch(() => {
444
+ });
445
+ } else {
446
+ await retireFailedConnection(cacheKey, client, error);
403
447
  }
404
448
  throw error;
405
449
  } finally {
@@ -407,28 +451,39 @@ async function attemptModernCall(options, toolName, args) {
407
451
  });
408
452
  }
409
453
  }
454
+ function callContextFor(options) {
455
+ const { perRequest } = splitConnectionHeaders(options.customHeaders);
456
+ const requestTimeoutMs = resolveRequestTimeoutMs(options.requestTimeoutMs);
457
+ return {
458
+ ...Object.keys(perRequest).length > 0 && { headers: perRequest },
459
+ ...requestTimeoutMs !== void 0 && { requestTimeoutMs }
460
+ };
461
+ }
410
462
  async function tryCallModernMCPTool(agentUrl, toolName, args, authToken, debugLogs = [], customHeaders, options = {}) {
411
463
  return withSpan(
412
464
  "adcp.mcp.negotiate",
413
465
  { "adcp.tool": toolName, "http.url": agentUrl },
414
466
  () => signingContextStorage.run(
415
467
  options.signingContext,
416
- () => attemptModernCall(
417
- {
418
- agentUrl,
419
- authToken,
420
- customHeaders,
421
- debugLogs,
422
- signingContext: options.signingContext,
423
- authProvider: options.authProvider,
424
- signal: options.signal,
425
- requestTimeoutMs: options.requestTimeoutMs,
426
- fetchFn: options.fetchFn,
427
- allowPrivateIp: options.allowPrivateIp,
428
- handleLegacy: options.handleLegacy
429
- },
430
- toolName,
431
- args
468
+ () => runWithMCPCallContext(
469
+ callContextFor({ customHeaders, requestTimeoutMs: options.requestTimeoutMs }),
470
+ () => attemptModernCall(
471
+ {
472
+ agentUrl,
473
+ authToken,
474
+ customHeaders,
475
+ debugLogs,
476
+ signingContext: options.signingContext,
477
+ authProvider: options.authProvider,
478
+ signal: options.signal,
479
+ requestTimeoutMs: options.requestTimeoutMs,
480
+ fetchFn: options.fetchFn,
481
+ allowPrivateIp: options.allowPrivateIp,
482
+ handleLegacy: options.handleLegacy
483
+ },
484
+ toolName,
485
+ args
486
+ )
432
487
  )
433
488
  )
434
489
  );
@@ -454,26 +509,34 @@ async function probeModernMCPConnection(agentUrl, authToken, customHeaders, opti
454
509
  options.signingContext?.cacheKey,
455
510
  options.authProvider,
456
511
  options.fetchFn,
457
- options.signal,
458
- options.requestTimeoutMs,
459
512
  options.handleLegacy,
460
513
  options.allowPrivateIp
461
514
  );
515
+ const scoped = currentMCPConnectionScopeKey() !== void 0;
462
516
  let client;
463
- try {
464
- client = await createNegotiatedClient(cacheKey, connectionOptions, authHeaders, false);
465
- return { connected: true, era: client.getProtocolEra() };
466
- } catch (error) {
467
- modernDiscoveries.delete(cacheKey);
468
- if (is401Error(error) || isAbortOrTimeoutError(error)) throw error;
469
- const status = httpStatusOf(error);
470
- if (status === 404 || status === 405) return { connected: false };
471
- if (isLegacyEraNegotiationFailure(error, status)) return { connected: false };
472
- throw error;
473
- } finally {
474
- if (client) await closeModernClient(client).catch(() => {
475
- });
476
- }
517
+ return runWithMCPCallContext(
518
+ callContextFor({ customHeaders, requestTimeoutMs: options.requestTimeoutMs }),
519
+ async () => {
520
+ try {
521
+ client = scoped ? await getOrCreateModernConnection(cacheKey, connectionOptions, authHeaders) : await createNegotiatedClient(cacheKey, connectionOptions, authHeaders, false);
522
+ const era = client.getProtocolEra();
523
+ if (scoped && era === "legacy" && connectionOptions.handleLegacy !== true) markKnownLegacy(cacheKey);
524
+ return { connected: true, era };
525
+ } catch (error) {
526
+ modernDiscoveries.delete(cacheKey);
527
+ if (is401Error(error) || isAbortOrTimeoutError(error)) throw error;
528
+ const status = httpStatusOf(error);
529
+ if (status === 404 || status === 405) return { connected: false };
530
+ if (isLegacyEraNegotiationFailure(error, status)) return { connected: false };
531
+ throw error;
532
+ } finally {
533
+ if (client && !(scoped && modernConnections.get(cacheKey) === client)) {
534
+ await closeModernClient(client).catch(() => {
535
+ });
536
+ }
537
+ }
538
+ }
539
+ );
477
540
  }
478
541
  async function tryListModernMCPTools(agentUrl, authToken, customHeaders, options = {}) {
479
542
  const connectionOptions = {
@@ -486,7 +549,8 @@ async function tryListModernMCPTools(agentUrl, authToken, customHeaders, options
486
549
  signal: options.signal,
487
550
  requestTimeoutMs: options.requestTimeoutMs,
488
551
  fetchFn: options.fetchFn,
489
- allowPrivateIp: options.allowPrivateIp
552
+ allowPrivateIp: options.allowPrivateIp,
553
+ handleLegacy: options.handleLegacy
490
554
  };
491
555
  const authHeaders = buildAuthHeaders(authToken, customHeaders, options.authProvider);
492
556
  const cacheKey = connectionCacheKey(
@@ -495,50 +559,75 @@ async function tryListModernMCPTools(agentUrl, authToken, customHeaders, options
495
559
  options.signingContext?.cacheKey,
496
560
  options.authProvider,
497
561
  options.fetchFn,
498
- options.signal,
499
- options.requestTimeoutMs,
500
562
  options.handleLegacy,
501
563
  options.allowPrivateIp
502
564
  );
565
+ const scoped = currentMCPConnectionScopeKey() !== void 0;
566
+ if (scoped && isKnownLegacy(cacheKey)) return { handled: false };
503
567
  let client;
568
+ const sharedClients = /* @__PURE__ */ new WeakSet();
569
+ const acquire = async (create) => {
570
+ const acquired = await create();
571
+ if (scoped && modernConnections.get(cacheKey) === acquired) sharedClients.add(acquired);
572
+ return acquired;
573
+ };
504
574
  const listTools = async (connectedClient) => {
505
- if (connectedClient.getProtocolEra() !== "modern") return { handled: false };
575
+ if (connectedClient.getProtocolEra() !== "modern" && connectionOptions.handleLegacy !== true) {
576
+ if (scoped) markKnownLegacy(cacheKey);
577
+ return { handled: false };
578
+ }
506
579
  const resolvedRequestTimeoutMs = resolveClientRequestTimeoutMs(options.requestTimeoutMs);
507
- const result = await connectedClient.listTools(
508
- options.adcpVersion === void 0 ? void 0 : { _meta: { adcp_version: options.adcpVersion } },
509
- {
510
- ...options.signal && { signal: options.signal },
511
- ...resolvedRequestTimeoutMs !== void 0 && { timeout: resolvedRequestTimeoutMs }
512
- }
580
+ const result = await trackConnectionUse(
581
+ connectedClient,
582
+ () => runCallPhase(
583
+ options.signal,
584
+ () => connectedClient.listTools(
585
+ options.adcpVersion === void 0 ? void 0 : { _meta: { adcp_version: options.adcpVersion } },
586
+ {
587
+ ...options.signal && { signal: options.signal },
588
+ ...resolvedRequestTimeoutMs !== void 0 && { timeout: resolvedRequestTimeoutMs }
589
+ }
590
+ )
591
+ )
513
592
  );
514
593
  return { handled: true, tools: result.tools };
515
594
  };
516
- try {
517
- client = await createNegotiatedClient(cacheKey, connectionOptions, authHeaders);
518
- return await listTools(client);
519
- } catch (error) {
520
- let failure = error;
521
- modernDiscoveries.delete(cacheKey);
522
- if (!is401Error(failure) && !isAbortOrTimeoutError(failure) && client && clientsUsingCachedDiscovery.has(client)) {
523
- await closeModernClient(client, false).catch(() => {
524
- });
595
+ return runWithMCPCallContext(
596
+ callContextFor({ customHeaders, requestTimeoutMs: options.requestTimeoutMs }),
597
+ async () => {
525
598
  try {
526
- client = await createNegotiatedClient(cacheKey, connectionOptions, authHeaders, false);
599
+ client = await acquire(
600
+ () => scoped ? getOrCreateModernConnection(cacheKey, connectionOptions, authHeaders) : createNegotiatedClient(cacheKey, connectionOptions, authHeaders)
601
+ );
527
602
  return await listTools(client);
528
- } catch (retryError) {
529
- failure = retryError;
603
+ } catch (error) {
604
+ let failure = error;
530
605
  modernDiscoveries.delete(cacheKey);
606
+ if (client && sharedClients.has(client)) {
607
+ await retireFailedConnection(cacheKey, client, failure);
608
+ }
609
+ if (!is401Error(failure) && !isAbortOrTimeoutError(failure) && client && clientsUsingCachedDiscovery.has(client)) {
610
+ if (!sharedClients.has(client)) await closeModernClient(client, false).catch(() => {
611
+ });
612
+ try {
613
+ client = await acquire(() => createNegotiatedClient(cacheKey, connectionOptions, authHeaders, false));
614
+ return await listTools(client);
615
+ } catch (retryError) {
616
+ failure = retryError;
617
+ modernDiscoveries.delete(cacheKey);
618
+ }
619
+ }
620
+ if (is401Error(failure) || isAbortOrTimeoutError(failure)) throw failure;
621
+ const status = httpStatusOf(failure);
622
+ if (status === 404 || status === 405) return { handled: false };
623
+ if (isLegacyEraNegotiationFailure(failure, status)) return { handled: false };
624
+ throw failure;
625
+ } finally {
626
+ if (client && !sharedClients.has(client)) await closeModernClient(client).catch(() => {
627
+ });
531
628
  }
532
629
  }
533
- if (is401Error(failure) || isAbortOrTimeoutError(failure)) throw failure;
534
- const status = httpStatusOf(failure);
535
- if (status === 404 || status === 405) return { handled: false };
536
- if (isLegacyEraNegotiationFailure(failure, status)) return { handled: false };
537
- throw failure;
538
- } finally {
539
- if (client) await closeModernClient(client).catch(() => {
540
- });
541
- }
630
+ );
542
631
  }
543
632
  async function closeModernMCPConnections() {
544
633
  connectionGeneration++;
@@ -13,6 +13,21 @@ export declare function closeCurrentMCPConnectionScope(): Promise<boolean>;
13
13
  * Isolate connection reuse to one runner/workflow and close it on every exit.
14
14
  * Nested callers join the existing scope so a storyboard still gets exactly
15
15
  * one reusable session rather than one session per helper layer.
16
+ *
17
+ * Every MCP call made inside `fn` — endpoint probe, capability discovery,
18
+ * `tools/list`, and tool calls — reuses one negotiated session per
19
+ * (endpoint, credential, tenant headers, signing identity, transport policy).
20
+ * Per-call correlation headers, `AbortSignal`s and timeouts do not choose the
21
+ * session. The scope terminates its sessions once, when `fn` settles; pass
22
+ * `{ isolate: true }` to force a fresh scope even when called from inside one.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * await withMCPConnectionScope(async () => {
27
+ * await client.syncAccounts(accounts);
28
+ * await client.getProducts(brief); // same MCP session, one `initialize`
29
+ * });
30
+ * ```
16
31
  */
17
32
  export declare function withMCPConnectionScope<T>(fn: () => Promise<T>, options?: {
18
33
  isolate?: boolean;
@@ -13,6 +13,21 @@ export declare function closeCurrentMCPConnectionScope(): Promise<boolean>;
13
13
  * Isolate connection reuse to one runner/workflow and close it on every exit.
14
14
  * Nested callers join the existing scope so a storyboard still gets exactly
15
15
  * one reusable session rather than one session per helper layer.
16
+ *
17
+ * Every MCP call made inside `fn` — endpoint probe, capability discovery,
18
+ * `tools/list`, and tool calls — reuses one negotiated session per
19
+ * (endpoint, credential, tenant headers, signing identity, transport policy).
20
+ * Per-call correlation headers, `AbortSignal`s and timeouts do not choose the
21
+ * session. The scope terminates its sessions once, when `fn` settles; pass
22
+ * `{ isolate: true }` to force a fresh scope even when called from inside one.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * await withMCPConnectionScope(async () => {
27
+ * await client.syncAccounts(accounts);
28
+ * await client.getProducts(brief); // same MCP session, one `initialize`
29
+ * });
30
+ * ```
16
31
  */
17
32
  export declare function withMCPConnectionScope<T>(fn: () => Promise<T>, options?: {
18
33
  isolate?: boolean;
@@ -43,8 +43,10 @@ export interface MCPCallOptions {
43
43
  /**
44
44
  * Scoped fetch implementation used for MCP requests, OAuth discovery, and token exchange.
45
45
  * Direct SDK calls retain isolated one-shot connections. Runner workflows opt into
46
- * scoped reuse, where the exact fetch function, cancellation signal, timeout,
47
- * credential, and headers all participate in connection identity.
46
+ * scoped reuse, where the exact fetch function, credential, signing identity,
47
+ * tenant/routing headers and transport policy participate in connection identity.
48
+ * The cancellation signal, timeout and correlation headers are per call: they
49
+ * bound that call's requests and never choose or split the session.
48
50
  */
49
51
  fetchFn?: typeof fetch;
50
52
  /** Explicitly allow private/loopback agent addresses for this connection. */
@@ -43,8 +43,10 @@ export interface MCPCallOptions {
43
43
  /**
44
44
  * Scoped fetch implementation used for MCP requests, OAuth discovery, and token exchange.
45
45
  * Direct SDK calls retain isolated one-shot connections. Runner workflows opt into
46
- * scoped reuse, where the exact fetch function, cancellation signal, timeout,
47
- * credential, and headers all participate in connection identity.
46
+ * scoped reuse, where the exact fetch function, credential, signing identity,
47
+ * tenant/routing headers and transport policy participate in connection identity.
48
+ * The cancellation signal, timeout and correlation headers are per call: they
49
+ * bound that call's requests and never choose or split the session.
48
50
  */
49
51
  fetchFn?: typeof fetch;
50
52
  /** Explicitly allow private/loopback agent addresses for this connection. */