@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
@@ -35,6 +35,20 @@ import {
35
35
  registerMCPConnectionScopePending
36
36
  } from "./mcp-scope.mjs";
37
37
  import { terminateSessionBestEffort } from "./session-termination.mjs";
38
+ import {
39
+ applyMCPCallHeaders,
40
+ isCorrelationHeader,
41
+ withAmbientIdentityHeaders,
42
+ closeWhenIdle,
43
+ linkedCallSignal,
44
+ joinPendingConnection,
45
+ RETRY_CONNECT,
46
+ requestTimeoutFor,
47
+ runCallPhase,
48
+ runWithMCPCallContext,
49
+ splitConnectionHeaders,
50
+ trackConnectionUse
51
+ } from "./mcp-call-context.mjs";
38
52
  import { createAgentTransportFetch } from "../net/agent-transport-fetch.mjs";
39
53
  const connectionCache = /* @__PURE__ */ new Map();
40
54
  const pendingConnections = /* @__PURE__ */ new Map();
@@ -43,11 +57,9 @@ const pendingOAuthConnections = /* @__PURE__ */ new Map();
43
57
  const streamableTransports = /* @__PURE__ */ new WeakMap();
44
58
  const oauthProviderIds = /* @__PURE__ */ new WeakMap();
45
59
  const transportFetchFnIds = /* @__PURE__ */ new WeakMap();
46
- const transportSignalIds = /* @__PURE__ */ new WeakMap();
47
60
  const MAX_CACHED_CONNECTIONS = 20;
48
61
  let nextOAuthProviderId = 0;
49
62
  let nextTransportFetchFnId = 0;
50
- let nextTransportSignalId = 0;
51
63
  let connectionGeneration = 0;
52
64
  let oauthConnectionGeneration = 0;
53
65
  async function closeMCPClient(client, terminateSession = true) {
@@ -67,7 +79,7 @@ function trackStreamableHTTPUrl(url) {
67
79
  if (oldest) knownStreamableHTTPUrls.delete(oldest);
68
80
  }
69
81
  }
70
- function connectionCacheKey(agentUrl, authToken, signingCacheKey, authHeaders, transportFetch, signal, requestTimeoutMs, allowPrivateIp) {
82
+ function connectionCacheKey(agentUrl, authToken, signingCacheKey, authHeaders, transportFetch, allowPrivateIp) {
71
83
  const parts = [agentUrl];
72
84
  const fingerprint = authToken ?? extractAuthHeader(authHeaders);
73
85
  if (fingerprint) parts.push(cacheDisambiguator(fingerprint));
@@ -75,8 +87,6 @@ function connectionCacheKey(agentUrl, authToken, signingCacheKey, authHeaders, t
75
87
  if (headersKey) parts.push(`headers:${headersKey}`);
76
88
  if (signingCacheKey) parts.push(signingCacheKey);
77
89
  if (transportFetch) parts.push(`fetch:${getTransportFetchFnDisambiguator(transportFetch)}`);
78
- if (signal) parts.push(`signal:${getTransportSignalDisambiguator(signal)}`);
79
- if (requestTimeoutMs !== void 0) parts.push(`timeout:${requestTimeoutMs}`);
80
90
  if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
81
91
  const scopeKey = currentMCPConnectionScopeKey();
82
92
  if (scopeKey) parts.push(scopeKey);
@@ -94,18 +104,19 @@ function extractAuthHeader(headers) {
94
104
  return void 0;
95
105
  }
96
106
  function headersCacheDisambiguator(headers) {
97
- const entries = Object.entries(headers ?? {}).filter(([key]) => {
98
- const lower = key.toLowerCase();
99
- return lower !== "traceparent" && lower !== "tracestate" && lower !== "baggage";
100
- }).map(([key, value]) => [key.toLowerCase(), value]).sort(([a], [b]) => a.localeCompare(b));
107
+ const entries = Object.entries(splitConnectionHeaders(headers).identity).map(([key, value]) => [key.toLowerCase(), value]).sort(([a], [b]) => a.localeCompare(b));
101
108
  return entries.length > 0 ? cacheDisambiguator(JSON.stringify(entries)) : void 0;
102
109
  }
103
- function withPerRequestTraceHeaders(fetchImpl) {
110
+ function withPerRequestTraceHeaders(fetchImpl, directDefaults) {
104
111
  return (input, init) => {
105
112
  const headers = new Headers(input instanceof Request ? input.headers : void 0);
106
113
  new Headers(init?.headers).forEach((value, key) => headers.set(key, value));
107
- for (const [key, value] of Object.entries(injectTraceHeaders())) headers.set(key, value);
108
- return fetchImpl(input, { ...init, headers });
114
+ applyMCPCallHeaders(headers, directDefaults);
115
+ if (headers.has("traceparent")) return fetchImpl(input, { ...init, headers, ...linkedCallSignal(init) });
116
+ for (const [key, value] of Object.entries(injectTraceHeaders())) {
117
+ if (isCorrelationHeader(key) && !headers.has(key)) headers.set(key, value);
118
+ }
119
+ return fetchImpl(input, { ...init, headers, ...linkedCallSignal(init) });
109
120
  };
110
121
  }
111
122
  function getCachedConnection(key) {
@@ -122,7 +133,7 @@ function evictLeastRecentlyUsed() {
122
133
  if (!lruKey) return;
123
134
  const oldClient = connectionCache.get(lruKey);
124
135
  connectionCache.delete(lruKey);
125
- if (oldClient) void closeMCPClient(oldClient).catch(() => {
136
+ if (oldClient) void closeWhenIdle(oldClient, () => closeMCPClient(oldClient)).catch(() => {
126
137
  });
127
138
  }
128
139
  function evictLeastRecentlyUsedOAuth() {
@@ -131,7 +142,7 @@ function evictLeastRecentlyUsedOAuth() {
131
142
  if (!lruKey) return;
132
143
  const oldClient = oauthConnectionCache.get(lruKey);
133
144
  oauthConnectionCache.delete(lruKey);
134
- if (oldClient) void closeMCPClient(oldClient).catch(() => {
145
+ if (oldClient) void closeWhenIdle(oldClient, () => closeMCPClient(oldClient)).catch(() => {
135
146
  });
136
147
  }
137
148
  async function closeOAuthConnections() {
@@ -172,31 +183,37 @@ async function closeMCPConnections() {
172
183
  await closeModernMCPConnections();
173
184
  }
174
185
  async function getOrCreateConnection(cacheKey, baseUrl, authHeaders, debugLogs, label, transportFetch, requestOptions = {}) {
175
- const cached = getCachedConnection(cacheKey);
176
- if (cached) return cached;
177
- const pending = pendingConnections.get(cacheKey);
178
- if (pending) return pending;
179
- const generation = connectionGeneration;
180
- const promise = connectMCPWithFallback(baseUrl, authHeaders, debugLogs, label, transportFetch, requestOptions).then(async (client) => {
181
- if (generation !== connectionGeneration) {
182
- await closeMCPClient(client).catch(() => {
183
- });
184
- throw new Error(`MCP ${label} completed after connection teardown`);
186
+ for (let attempt = 0; ; attempt++) {
187
+ const cached = getCachedConnection(cacheKey);
188
+ if (cached) return cached;
189
+ const pending = pendingConnections.get(cacheKey);
190
+ if (pending) {
191
+ const joined = await joinPendingConnection(pending, requestOptions, attempt);
192
+ if (joined === RETRY_CONNECT) continue;
193
+ return joined;
185
194
  }
186
- connectionCache.set(cacheKey, client);
187
- registerMCPConnectionScopeCleanup("legacy", cacheKey, async () => {
188
- if (connectionCache.get(cacheKey) !== client) return;
189
- connectionCache.delete(cacheKey);
190
- await closeMCPClient(client);
195
+ const generation = connectionGeneration;
196
+ const promise = connectMCPWithFallback(baseUrl, authHeaders, debugLogs, label, transportFetch, requestOptions).then(async (client) => {
197
+ if (generation !== connectionGeneration) {
198
+ await closeMCPClient(client).catch(() => {
199
+ });
200
+ throw new Error(`MCP ${label} completed after connection teardown`);
201
+ }
202
+ connectionCache.set(cacheKey, client);
203
+ registerMCPConnectionScopeCleanup("legacy", cacheKey, async () => {
204
+ if (connectionCache.get(cacheKey) !== client) return;
205
+ connectionCache.delete(cacheKey);
206
+ await closeMCPClient(client);
207
+ });
208
+ evictLeastRecentlyUsed();
209
+ return client;
210
+ }).finally(() => {
211
+ if (pendingConnections.get(cacheKey) === promise) pendingConnections.delete(cacheKey);
191
212
  });
192
- evictLeastRecentlyUsed();
193
- return client;
194
- }).finally(() => {
195
- if (pendingConnections.get(cacheKey) === promise) pendingConnections.delete(cacheKey);
196
- });
197
- pendingConnections.set(cacheKey, promise);
198
- registerMCPConnectionScopePending(promise);
199
- return promise;
213
+ pendingConnections.set(cacheKey, promise);
214
+ registerMCPConnectionScopePending(promise);
215
+ return promise;
216
+ }
200
217
  }
201
218
  function getOAuthProviderDisambiguator(authProvider) {
202
219
  let id = oauthProviderIds.get(authProvider);
@@ -214,25 +231,15 @@ function getTransportFetchFnDisambiguator(fetchFn) {
214
231
  }
215
232
  return id;
216
233
  }
217
- function getTransportSignalDisambiguator(signal) {
218
- let id = transportSignalIds.get(signal);
219
- if (!id) {
220
- id = cacheDisambiguator(`transport-signal:${++nextTransportSignalId}`);
221
- transportSignalIds.set(signal, id);
222
- }
223
- return id;
224
- }
225
234
  function customHeadersDisambiguator(customHeaders) {
226
235
  return headersCacheDisambiguator(customHeaders);
227
236
  }
228
- function oauthConnectionCacheKey(agentUrl, authProvider, signingCacheKey, customHeaders, fetchFn, signal, requestTimeoutMs, allowPrivateIp) {
237
+ function oauthConnectionCacheKey(agentUrl, authProvider, signingCacheKey, customHeaders, fetchFn, allowPrivateIp) {
229
238
  const parts = [`${agentUrl}::oauth:${getOAuthProviderDisambiguator(authProvider)}`];
230
239
  if (signingCacheKey) parts.push(signingCacheKey);
231
240
  const headersKey = customHeadersDisambiguator(customHeaders);
232
241
  if (headersKey) parts.push(`headers:${headersKey}`);
233
242
  if (fetchFn) parts.push(`fetch:${getTransportFetchFnDisambiguator(fetchFn)}`);
234
- if (signal) parts.push(`signal:${getTransportSignalDisambiguator(signal)}`);
235
- if (requestTimeoutMs !== void 0) parts.push(`timeout:${requestTimeoutMs}`);
236
243
  if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
237
244
  const scopeKey = currentMCPConnectionScopeKey();
238
245
  if (scopeKey) parts.push(scopeKey);
@@ -247,39 +254,52 @@ function getCachedOAuthConnection(key) {
247
254
  return client;
248
255
  }
249
256
  async function getOrCreateOAuthConnection(cacheKey, options) {
250
- const cached = getCachedOAuthConnection(cacheKey);
251
- if (cached) return cached;
252
- const pending = pendingOAuthConnections.get(cacheKey);
253
- if (pending) return pending;
254
- const generation = oauthConnectionGeneration;
255
- const promise = connectMCP(options).then(async ({ client }) => {
256
- if (generation !== oauthConnectionGeneration) {
257
- await closeMCPClient(client).catch(() => {
258
- });
259
- throw new Error("OAuth MCP connection completed after connection teardown");
257
+ for (let attempt = 0; ; attempt++) {
258
+ const cached = getCachedOAuthConnection(cacheKey);
259
+ if (cached) return cached;
260
+ const pending = pendingOAuthConnections.get(cacheKey);
261
+ if (pending) {
262
+ const joined = await joinPendingConnection(pending, options, attempt);
263
+ if (joined === RETRY_CONNECT) continue;
264
+ return joined;
260
265
  }
261
- oauthConnectionCache.set(cacheKey, client);
262
- registerMCPConnectionScopeCleanup("oauth", cacheKey, async () => {
263
- if (oauthConnectionCache.get(cacheKey) !== client) return;
264
- oauthConnectionCache.delete(cacheKey);
265
- await closeMCPClient(client);
266
+ const generation = oauthConnectionGeneration;
267
+ const promise = connectMCP(options).then(async ({ client }) => {
268
+ if (generation !== oauthConnectionGeneration) {
269
+ await closeMCPClient(client).catch(() => {
270
+ });
271
+ throw new Error("OAuth MCP connection completed after connection teardown");
272
+ }
273
+ oauthConnectionCache.set(cacheKey, client);
274
+ registerMCPConnectionScopeCleanup("oauth", cacheKey, async () => {
275
+ if (oauthConnectionCache.get(cacheKey) !== client) return;
276
+ oauthConnectionCache.delete(cacheKey);
277
+ await closeMCPClient(client);
278
+ });
279
+ evictLeastRecentlyUsedOAuth();
280
+ return client;
281
+ }).finally(() => {
282
+ if (pendingOAuthConnections.get(cacheKey) === promise) pendingOAuthConnections.delete(cacheKey);
266
283
  });
267
- evictLeastRecentlyUsedOAuth();
268
- return client;
269
- }).finally(() => {
270
- if (pendingOAuthConnections.get(cacheKey) === promise) pendingOAuthConnections.delete(cacheKey);
271
- });
272
- pendingOAuthConnections.set(cacheKey, promise);
273
- registerMCPConnectionScopePending(promise);
274
- return promise;
284
+ pendingOAuthConnections.set(cacheKey, promise);
285
+ registerMCPConnectionScopePending(promise);
286
+ return promise;
287
+ }
275
288
  }
276
289
  async function withCachedOAuthConnection(options, label, fn) {
277
- const guardedConnection = options.signal !== void 0 || options.requestTimeoutMs !== void 0 || options.fetchFn !== void 0;
290
+ options = { ...options, customHeaders: withAmbientIdentityHeaders(options.customHeaders) };
291
+ return runWithMCPCallContext(
292
+ callContextFor(options.customHeaders, options.requestTimeoutMs),
293
+ () => withCachedOAuthConnectionImpl(options, label, fn)
294
+ );
295
+ }
296
+ async function withCachedOAuthConnectionImpl(options, label, fn) {
297
+ const guardedConnection = options.fetchFn !== void 0;
278
298
  if (guardedConnection && currentMCPConnectionScopeKey() === void 0) {
279
299
  const { client } = await connectMCP(options);
280
300
  let succeeded = false;
281
301
  try {
282
- const result = await fn(client);
302
+ const result = await runCallPhase(options.signal, () => fn(client));
283
303
  succeeded = true;
284
304
  return result;
285
305
  } finally {
@@ -293,13 +313,11 @@ async function withCachedOAuthConnection(options, label, fn) {
293
313
  options.signingContext?.cacheKey,
294
314
  options.customHeaders,
295
315
  options.fetchFn,
296
- options.signal,
297
- options.requestTimeoutMs,
298
316
  options.allowPrivateIp
299
317
  );
300
318
  const mcpClient = await getOrCreateOAuthConnection(cacheKey, options);
301
319
  try {
302
- return await fn(mcpClient);
320
+ return await trackConnectionUse(mcpClient, () => runCallPhase(options.signal, () => fn(mcpClient)));
303
321
  } catch (error) {
304
322
  const errorMessage = error instanceof Error ? error.message : String(error);
305
323
  options.debugLogs.push({
@@ -308,52 +326,38 @@ async function withCachedOAuthConnection(options, label, fn) {
308
326
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
309
327
  error
310
328
  });
329
+ if (isAbortOrTimeoutError(error)) throw error;
311
330
  if (is401Error(error)) {
312
- oauthConnectionCache.delete(cacheKey);
313
- try {
314
- await closeMCPClient(mcpClient, false);
315
- } catch {
316
- }
317
331
  options.debugLogs.push({
318
332
  type: "warning",
319
333
  message: `MCP: OAuth authentication issue detected for ${label}; evicted cached connection`,
320
334
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
321
335
  });
322
- throw error;
323
- }
324
- if (isAbortOrTimeoutError(error)) {
325
- oauthConnectionCache.delete(cacheKey);
326
- try {
327
- await closeMCPClient(mcpClient, false);
328
- } catch {
329
- }
330
- throw error;
331
- }
332
- if (httpStatusOf(error) === 404) {
333
- oauthConnectionCache.delete(cacheKey);
334
- try {
335
- await closeMCPClient(mcpClient, false);
336
- } catch {
337
- }
338
- throw error;
339
- }
340
- oauthConnectionCache.delete(cacheKey);
341
- try {
342
- await closeMCPClient(mcpClient, false);
343
- } catch {
344
336
  }
337
+ await retireLegacyConnection(oauthConnectionCache, cacheKey, mcpClient);
345
338
  throw error;
346
339
  }
347
340
  }
348
341
  async function withCachedConnection(agentUrl, authToken, authHeaders, debugLogs, label, fn, transportFetch, requestOptions = {}) {
342
+ authHeaders = withAmbientIdentityHeaders(authHeaders);
343
+ return runWithMCPCallContext(
344
+ callContextFor(authHeaders, requestOptions.requestTimeoutMs),
345
+ () => withCachedConnectionImpl(agentUrl, authToken, authHeaders, debugLogs, label, fn, transportFetch, requestOptions)
346
+ );
347
+ }
348
+ async function withCachedConnectionImpl(agentUrl, authToken, authHeaders, debugLogs, label, fn, transportFetch, requestOptions) {
349
349
  const signingContext = signingContextStorage.getStore();
350
350
  const baseUrl = new URL(agentUrl);
351
- const guardedConnection = transportFetch !== void 0 || requestOptions.signal !== void 0 || requestOptions.requestTimeoutMs !== void 0;
351
+ const guardedConnection = transportFetch !== void 0;
352
352
  if (guardedConnection && currentMCPConnectionScopeKey() === void 0) {
353
353
  const client = await connectMCPWithFallback(baseUrl, authHeaders, debugLogs, label, transportFetch, requestOptions);
354
354
  let succeeded = false;
355
355
  try {
356
- const result = await withAbortSignal([requestOptions.signal], void 0, () => fn(client));
356
+ const result = await withAbortSignal(
357
+ [requestOptions.signal],
358
+ void 0,
359
+ () => runCallPhase(requestOptions.signal, () => fn(client))
360
+ );
357
361
  succeeded = true;
358
362
  return result;
359
363
  } finally {
@@ -367,8 +371,6 @@ async function withCachedConnection(agentUrl, authToken, authHeaders, debugLogs,
367
371
  signingContext?.cacheKey,
368
372
  authHeaders,
369
373
  transportFetch,
370
- requestOptions.signal,
371
- requestOptions.requestTimeoutMs,
372
374
  requestOptions.allowPrivateIp
373
375
  );
374
376
  const mcpClient = await getOrCreateConnection(
@@ -381,7 +383,7 @@ async function withCachedConnection(agentUrl, authToken, authHeaders, debugLogs,
381
383
  requestOptions
382
384
  );
383
385
  try {
384
- return await fn(mcpClient);
386
+ return await trackConnectionUse(mcpClient, () => runCallPhase(requestOptions.signal, () => fn(mcpClient)));
385
387
  } catch (error) {
386
388
  const errorMessage = error instanceof Error ? error.message : String(error);
387
389
  debugLogs.push({
@@ -390,48 +392,63 @@ async function withCachedConnection(agentUrl, authToken, authHeaders, debugLogs,
390
392
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
391
393
  error
392
394
  });
395
+ if (isAbortOrTimeoutError(error)) throw error;
393
396
  if (is401Error(error)) {
394
- connectionCache.delete(cacheKey);
395
- try {
396
- await closeMCPClient(mcpClient, false);
397
- } catch {
398
- }
399
397
  debugLogs.push({
400
398
  type: "warning",
401
399
  message: `MCP: Authentication issue detected for ${label} - headers may not be reaching server`,
402
400
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
403
401
  });
404
- throw error;
405
- }
406
- if (isAbortOrTimeoutError(error)) {
407
- connectionCache.delete(cacheKey);
408
- try {
409
- await closeMCPClient(mcpClient, false);
410
- } catch {
411
- }
412
- throw error;
413
- }
414
- if (httpStatusOf(error) === 404) {
415
- connectionCache.delete(cacheKey);
416
- try {
417
- await closeMCPClient(mcpClient, false);
418
- } catch {
419
- }
402
+ } else if (httpStatusOf(error) === 404) {
420
403
  debugLogs.push({
421
404
  type: "warning",
422
405
  message: `MCP: Session not found for ${label}; evicted cached connection without retry`,
423
406
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
424
407
  });
425
- throw error;
426
- }
427
- connectionCache.delete(cacheKey);
428
- try {
429
- await closeMCPClient(mcpClient, false);
430
- } catch {
431
408
  }
409
+ await retireLegacyConnection(connectionCache, cacheKey, mcpClient);
432
410
  throw error;
433
411
  }
434
412
  }
413
+ async function withScopedLegacyConnection(options, label, fn) {
414
+ if (currentMCPConnectionScopeKey() === void 0) return void 0;
415
+ const debugLogs = options.debugLogs ?? [];
416
+ if (options.authProvider) {
417
+ return {
418
+ value: await withCachedOAuthConnection({ ...options, authProvider: options.authProvider, debugLogs }, label, fn)
419
+ };
420
+ }
421
+ const authHeaders = createMCPRequestHeaders(options.customHeaders, options.authToken);
422
+ return {
423
+ value: await withCachedConnection(
424
+ options.agentUrl,
425
+ options.authToken,
426
+ authHeaders,
427
+ debugLogs,
428
+ label,
429
+ fn,
430
+ options.fetchFn,
431
+ {
432
+ signal: options.signal,
433
+ requestTimeoutMs: options.requestTimeoutMs,
434
+ allowPrivateIp: options.allowPrivateIp
435
+ }
436
+ )
437
+ };
438
+ }
439
+ function callContextFor(headers, requestTimeoutMs) {
440
+ const { perRequest } = splitConnectionHeaders(headers);
441
+ const timeoutMs = resolveRequestTimeoutMs(requestTimeoutMs);
442
+ return {
443
+ ...Object.keys(perRequest).length > 0 && { headers: perRequest },
444
+ ...timeoutMs !== void 0 && { requestTimeoutMs: timeoutMs }
445
+ };
446
+ }
447
+ async function retireLegacyConnection(cache, cacheKey, client) {
448
+ if (cache.get(cacheKey) === client) cache.delete(cacheKey);
449
+ await closeWhenIdle(client, () => closeMCPClient(client, false)).catch(() => {
450
+ });
451
+ }
435
452
  function httpStatusOf(error, depth = 0) {
436
453
  if (!error || typeof error !== "object" || depth > 4) return void 0;
437
454
  const candidate = error;
@@ -441,6 +458,7 @@ function httpStatusOf(error, depth = 0) {
441
458
  return httpStatusOf(candidate.cause, depth + 1);
442
459
  }
443
460
  async function connectMCPWithFallback(url, authHeaders, debugLogs = [], label = "connection", transportFetch, requestOptions = {}) {
461
+ authHeaders = withAmbientIdentityHeaders(authHeaders);
444
462
  return withSpan(
445
463
  "adcp.mcp.connect",
446
464
  {
@@ -463,11 +481,15 @@ async function connectMCPWithFallbackImpl(url, authHeaders, debugLogs = [], labe
463
481
  trustedFetchFn: transportFetch,
464
482
  allowPrivateIp: requestOptions.allowPrivateIp
465
483
  });
466
- const networkFetch = (input, init) => {
467
- const signals = [init?.signal ?? void 0].filter((signal2) => signal2 !== void 0);
468
- const signal = signals.length === 0 ? void 0 : signals.length === 1 ? signals[0] : AbortSignal.any(signals);
469
- return rawNetworkFetch(input, { ...init, signal });
470
- };
484
+ const networkFetch = (input, init) => (
485
+ // Cancellation stays linked for the entire response-body lifetime, through
486
+ // `init.signal`: the v1 transport does not forward a request's own signal, so
487
+ // the active call's signal is linked to that call's POST at the outermost
488
+ // transport wrapper (aborting one call cancels its HTTP request without
489
+ // closing a session other calls share). The background GET/SSE listener
490
+ // never carries a call signal.
491
+ rawNetworkFetch(input, { ...init, signal: init?.signal ?? void 0 })
492
+ );
471
493
  const sizeLimited = wrapFetchWithSizeLimit(networkFetch);
472
494
  const diagnosticFetch = wrapFetchWithTransportDiagnostics(sizeLimited);
473
495
  const baseFetch = signingContext ? buildAgentSigningFetch({
@@ -477,8 +499,8 @@ async function connectMCPWithFallbackImpl(url, authHeaders, debugLogs = [], labe
477
499
  adcpVersion: signingContext.adcpVersion
478
500
  }) : diagnosticFetch;
479
501
  const transportOptions = {
480
- requestInit: { headers: authHeaders, redirect: "manual" },
481
- fetch: wrapFetchWithCapture(withPerRequestTraceHeaders(baseFetch))
502
+ requestInit: { headers: splitConnectionHeaders(authHeaders).identity, redirect: "manual" },
503
+ fetch: wrapFetchWithCapture(withPerRequestTraceHeaders(baseFetch, splitConnectionHeaders(authHeaders).perRequest))
482
504
  };
483
505
  let failedClient;
484
506
  try {
@@ -572,8 +594,10 @@ async function connectMCPWithFallbackImpl(url, authHeaders, debugLogs = [], labe
572
594
  try {
573
595
  await client.connect(
574
596
  new SSEClientTransport(url, {
575
- requestInit: { headers: authHeaders, redirect: "manual" },
576
- fetch: wrapFetchWithCapture(withPerRequestTraceHeaders(baseFetch))
597
+ requestInit: { headers: splitConnectionHeaders(authHeaders).identity, redirect: "manual" },
598
+ fetch: wrapFetchWithCapture(
599
+ withPerRequestTraceHeaders(baseFetch, splitConnectionHeaders(authHeaders).perRequest)
600
+ )
577
601
  }),
578
602
  mcpRequestOptions
579
603
  );
@@ -720,11 +744,11 @@ async function connectMCP(options) {
720
744
  return normalized !== "authorization" && normalized !== "x-adcp-auth";
721
745
  })
722
746
  ) : customHeaders;
723
- const authHeaders = createMCPRequestHeaders(filteredCustomHeaders, authToken);
747
+ const authHeaders = withAmbientIdentityHeaders(createMCPRequestHeaders(filteredCustomHeaders, authToken));
724
748
  const hasNonAcceptCustomHeaders = Object.keys(filteredCustomHeaders ?? {}).some(
725
749
  (key) => key.toLowerCase() !== "accept"
726
750
  );
727
- transportOptions.requestInit = { headers: authHeaders, redirect: "manual" };
751
+ transportOptions.requestInit = { headers: splitConnectionHeaders(authHeaders).identity, redirect: "manual" };
728
752
  if (authProvider) {
729
753
  transportOptions.authProvider = authProvider;
730
754
  debugLogs.push({
@@ -745,18 +769,27 @@ async function connectMCP(options) {
745
769
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
746
770
  });
747
771
  }
748
- const requestTimeoutMs = resolveRequestTimeoutMs(configuredRequestTimeoutMs);
749
772
  const clientRequestTimeoutMs = resolveClientRequestTimeoutMs(configuredRequestTimeoutMs);
773
+ const directRequestTimeoutMs = resolveRequestTimeoutMs(configuredRequestTimeoutMs);
750
774
  const requestOptions = {
751
775
  ...signal && { signal },
752
776
  ...clientRequestTimeoutMs !== void 0 && { timeout: clientRequestTimeoutMs }
753
777
  };
754
778
  const rawNetworkFetch = createAgentTransportFetch(agentUrl, { trustedFetchFn: fetchFn, allowPrivateIp });
755
779
  const sizeLimited = wrapFetchWithSizeLimit(
756
- (input, init) => withAbortSignal(
757
- [init?.signal],
758
- requestTimeoutMs,
759
- (linkedSignal) => rawNetworkFetch(input, { ...init, signal: linkedSignal })
780
+ (input, init) => (
781
+ // Inside a call context the deadline is that call's; a direct caller who
782
+ // holds the returned client keeps the timeout configured on connect. The
783
+ // call's own signal is already linked into `init.signal` for the whole
784
+ // response body, so aborting one call cancels its streamed POST only.
785
+ withAbortSignal(
786
+ [init?.signal],
787
+ requestTimeoutFor(init, directRequestTimeoutMs),
788
+ (deadline) => rawNetworkFetch(input, {
789
+ ...init,
790
+ signal: init?.signal && deadline ? AbortSignal.any([init.signal, deadline]) : deadline ?? init?.signal
791
+ })
792
+ )
760
793
  )
761
794
  );
762
795
  const diagnosticFetch = wrapFetchWithTransportDiagnostics(sizeLimited);
@@ -766,7 +799,9 @@ async function connectMCP(options) {
766
799
  getCapability: signingContext.getCapability,
767
800
  adcpVersion: signingContext.adcpVersion
768
801
  }) : diagnosticFetch;
769
- transportOptions.fetch = wrapFetchWithCapture(withPerRequestTraceHeaders(signedFetch));
802
+ transportOptions.fetch = wrapFetchWithCapture(
803
+ withPerRequestTraceHeaders(signedFetch, splitConnectionHeaders(authHeaders).perRequest)
804
+ );
770
805
  const transport = new StreamableHTTPClientTransport(baseUrl, transportOptions);
771
806
  try {
772
807
  await mcpClient.connect(transport, requestOptions);
@@ -889,5 +924,6 @@ export {
889
924
  closeOAuthConnections,
890
925
  connectMCP,
891
926
  connectMCPWithFallback,
892
- withCachedConnection
927
+ withCachedConnection,
928
+ withScopedLegacyConnection
893
929
  };
@@ -1,4 +1,19 @@
1
- export type TransportActivityType = 'request_started' | 'response_received' | 'request_failed';
1
+ /**
2
+ * Lifecycle events for one transport request.
3
+ *
4
+ * - `request_started`: the request is about to be sent.
5
+ * - `response_received`: response **headers** arrived. For captured bodies,
6
+ * dispatch waits for the bounded preview. Its `durationMs` is time to
7
+ * headers, not time to completion; it does not mean the reply finished.
8
+ * - `response_completed`: the caller finished with a response body that was not
9
+ * captured (SSE, no finite declared length, non-text, or over the capture
10
+ * limit). Emitted at most once, with an `outcome`, total `bytes` and total
11
+ * `durationMs`.
12
+ * - `request_failed`: the request failed before response headers arrived.
13
+ */
14
+ export type TransportActivityType = 'request_started' | 'response_received' | 'response_completed' | 'request_failed';
15
+ /** How an uncaptured response body finished. */
16
+ export type TransportResponseOutcome = 'ended' | 'errored' | 'aborted' | 'cancelled';
2
17
  export interface TransportActivityContext {
3
18
  agentId: string;
4
19
  protocol: 'mcp' | 'a2a';
@@ -34,6 +49,23 @@ export interface TransportActivity {
34
49
  responseHeaders?: Record<string, string>;
35
50
  responseBody?: string;
36
51
  responseBodyTruncated?: boolean;
52
+ /**
53
+ * On `response_received`: `true` when the body is streamed uncaptured, so a
54
+ * `response_completed` event will follow once the caller finishes reading,
55
+ * cancels, or aborts it. The event then means "headers received", not
56
+ * "reply finished".
57
+ */
58
+ streaming?: boolean;
59
+ /**
60
+ * On `response_completed`: how the body ended. `ended` is EOF, `errored` is a
61
+ * stream error, `aborted` is an `AbortSignal` abort (including a cancel after the
62
+ * signal fired) or abort-style error, and `cancelled` is the consumer
63
+ * cancelling the body without an abort, including SDK cleanup after it has
64
+ * delivered a successful MCP result.
65
+ */
66
+ outcome?: TransportResponseOutcome;
67
+ /** On `response_completed`: body bytes that passed through before the outcome. */
68
+ bytes?: number;
37
69
  errorName?: string;
38
70
  errorMessage?: string;
39
71
  }
@@ -1,4 +1,19 @@
1
- export type TransportActivityType = 'request_started' | 'response_received' | 'request_failed';
1
+ /**
2
+ * Lifecycle events for one transport request.
3
+ *
4
+ * - `request_started`: the request is about to be sent.
5
+ * - `response_received`: response **headers** arrived. For captured bodies,
6
+ * dispatch waits for the bounded preview. Its `durationMs` is time to
7
+ * headers, not time to completion; it does not mean the reply finished.
8
+ * - `response_completed`: the caller finished with a response body that was not
9
+ * captured (SSE, no finite declared length, non-text, or over the capture
10
+ * limit). Emitted at most once, with an `outcome`, total `bytes` and total
11
+ * `durationMs`.
12
+ * - `request_failed`: the request failed before response headers arrived.
13
+ */
14
+ export type TransportActivityType = 'request_started' | 'response_received' | 'response_completed' | 'request_failed';
15
+ /** How an uncaptured response body finished. */
16
+ export type TransportResponseOutcome = 'ended' | 'errored' | 'aborted' | 'cancelled';
2
17
  export interface TransportActivityContext {
3
18
  agentId: string;
4
19
  protocol: 'mcp' | 'a2a';
@@ -34,6 +49,23 @@ export interface TransportActivity {
34
49
  responseHeaders?: Record<string, string>;
35
50
  responseBody?: string;
36
51
  responseBodyTruncated?: boolean;
52
+ /**
53
+ * On `response_received`: `true` when the body is streamed uncaptured, so a
54
+ * `response_completed` event will follow once the caller finishes reading,
55
+ * cancels, or aborts it. The event then means "headers received", not
56
+ * "reply finished".
57
+ */
58
+ streaming?: boolean;
59
+ /**
60
+ * On `response_completed`: how the body ended. `ended` is EOF, `errored` is a
61
+ * stream error, `aborted` is an `AbortSignal` abort (including a cancel after the
62
+ * signal fired) or abort-style error, and `cancelled` is the consumer
63
+ * cancelling the body without an abort, including SDK cleanup after it has
64
+ * delivered a successful MCP result.
65
+ */
66
+ outcome?: TransportResponseOutcome;
67
+ /** On `response_completed`: body bytes that passed through before the outcome. */
68
+ bytes?: number;
37
69
  errorName?: string;
38
70
  errorMessage?: string;
39
71
  }