@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.
- package/README.md +15 -0
- package/dist/lib/advanced.d.mts +2 -2
- package/dist/lib/advanced.d.ts +2 -2
- package/dist/lib/advanced.js +8 -2
- package/dist/lib/advanced.mjs +8 -2
- package/dist/lib/auth/oauth/index.d.mts +1 -0
- package/dist/lib/auth/oauth/index.d.ts +1 -0
- package/dist/lib/auth/oauth/index.js +5 -0
- package/dist/lib/auth/oauth/index.mjs +3 -0
- package/dist/lib/auth/oauth/pending-web-flow.d.mts +16 -0
- package/dist/lib/auth/oauth/pending-web-flow.d.ts +16 -0
- package/dist/lib/auth/oauth/pending-web-flow.js +66 -0
- package/dist/lib/auth/oauth/pending-web-flow.mjs +41 -0
- package/dist/lib/auth/oauth/web-flow.js +3 -3
- package/dist/lib/auth/oauth/web-flow.mjs +3 -3
- package/dist/lib/core/AgentClient.d.mts +4 -0
- package/dist/lib/core/AgentClient.d.ts +4 -0
- package/dist/lib/core/AgentClient.js +8 -0
- package/dist/lib/core/AgentClient.mjs +8 -0
- package/dist/lib/core/SingleAgentClient.d.mts +154 -0
- package/dist/lib/core/SingleAgentClient.d.ts +154 -0
- package/dist/lib/core/SingleAgentClient.js +661 -23
- package/dist/lib/core/SingleAgentClient.mjs +666 -23
- package/dist/lib/core/agent-discovery-cache.d.mts +103 -0
- package/dist/lib/core/agent-discovery-cache.d.ts +103 -0
- package/dist/lib/core/agent-discovery-cache.js +121 -0
- package/dist/lib/core/agent-discovery-cache.mjs +92 -0
- package/dist/lib/index.d.mts +5 -3
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.js +7 -0
- package/dist/lib/index.mjs +6 -0
- package/dist/lib/protocols/index.d.mts +2 -2
- package/dist/lib/protocols/index.d.ts +2 -2
- package/dist/lib/protocols/index.js +1 -1
- package/dist/lib/protocols/index.mjs +1 -1
- package/dist/lib/protocols/mcp-call-context.d.mts +109 -0
- package/dist/lib/protocols/mcp-call-context.d.ts +109 -0
- package/dist/lib/protocols/mcp-call-context.js +221 -0
- package/dist/lib/protocols/mcp-call-context.mjs +186 -0
- package/dist/lib/protocols/mcp-modern.d.mts +9 -1
- package/dist/lib/protocols/mcp-modern.d.ts +9 -1
- package/dist/lib/protocols/mcp-modern.js +180 -104
- package/dist/lib/protocols/mcp-modern.mjs +193 -104
- package/dist/lib/protocols/mcp-scope.d.mts +15 -0
- package/dist/lib/protocols/mcp-scope.d.ts +15 -0
- package/dist/lib/protocols/mcp.d.mts +4 -2
- package/dist/lib/protocols/mcp.d.ts +4 -2
- package/dist/lib/protocols/mcp.js +178 -154
- package/dist/lib/protocols/mcp.mjs +189 -153
- package/dist/lib/protocols/transportDiagnostics.d.mts +33 -1
- package/dist/lib/protocols/transportDiagnostics.d.ts +33 -1
- package/dist/lib/protocols/transportDiagnostics.js +128 -12
- package/dist/lib/protocols/transportDiagnostics.mjs +128 -12
- package/dist/lib/registry/types.generated.d.mts +63 -1
- package/dist/lib/registry/types.generated.d.ts +63 -1
- package/dist/lib/reporting/azure/index.d.mts +26 -0
- package/dist/lib/reporting/azure/index.d.ts +26 -0
- package/dist/lib/reporting/azure/index.js +247 -0
- package/dist/lib/reporting/azure/index.mjs +230 -0
- package/dist/lib/reporting/bigquery/index.d.mts +127 -0
- package/dist/lib/reporting/bigquery/index.d.ts +127 -0
- package/dist/lib/reporting/bigquery/index.js +379 -0
- package/dist/lib/reporting/bigquery/index.mjs +341 -0
- package/dist/lib/reporting/gcs/index.d.mts +2 -0
- package/dist/lib/reporting/gcs/index.d.ts +2 -0
- package/dist/lib/reporting/gcs/index.js +3 -0
- package/dist/lib/reporting/gcs/index.mjs +2 -0
- package/dist/lib/reporting/gcs/row-provider.d.mts +32 -0
- package/dist/lib/reporting/gcs/row-provider.d.ts +32 -0
- package/dist/lib/reporting/gcs/row-provider.js +205 -0
- package/dist/lib/reporting/gcs/row-provider.mjs +188 -0
- package/dist/lib/reporting/ledger/change-feed.d.mts +41 -0
- package/dist/lib/reporting/ledger/change-feed.d.ts +41 -0
- package/dist/lib/reporting/ledger/change-feed.js +101 -0
- package/dist/lib/reporting/ledger/change-feed.mjs +74 -0
- package/dist/lib/reporting/ledger/handler.js +22 -8
- package/dist/lib/reporting/ledger/handler.mjs +22 -8
- package/dist/lib/reporting/ledger/index.d.mts +4 -0
- package/dist/lib/reporting/ledger/index.d.ts +4 -0
- package/dist/lib/reporting/ledger/index.js +9 -1
- package/dist/lib/reporting/ledger/index.mjs +4 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +2 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +2 -0
- package/dist/lib/reporting/ledger/managed-postgres.js +21 -1
- package/dist/lib/reporting/ledger/managed-postgres.mjs +21 -1
- package/dist/lib/reporting/ledger/managed.d.mts +9 -0
- package/dist/lib/reporting/ledger/managed.d.ts +9 -0
- package/dist/lib/reporting/ledger/managed.js +18 -0
- package/dist/lib/reporting/ledger/managed.mjs +18 -0
- package/dist/lib/reporting/ledger/postgres.d.mts +185 -0
- package/dist/lib/reporting/ledger/postgres.d.ts +185 -0
- package/dist/lib/reporting/ledger/postgres.js +872 -26
- package/dist/lib/reporting/ledger/postgres.mjs +880 -26
- package/dist/lib/reporting/ledger/producer.js +10 -1
- package/dist/lib/reporting/ledger/producer.mjs +10 -1
- package/dist/lib/reporting/ledger/row-storage-errors.d.mts +7 -0
- package/dist/lib/reporting/ledger/row-storage-errors.d.ts +7 -0
- package/dist/lib/reporting/ledger/row-storage-errors.js +41 -0
- package/dist/lib/reporting/ledger/row-storage-errors.mjs +16 -0
- package/dist/lib/reporting/ledger/row-storage-migration.d.mts +41 -0
- package/dist/lib/reporting/ledger/row-storage-migration.d.ts +41 -0
- package/dist/lib/reporting/ledger/row-storage-migration.js +234 -0
- package/dist/lib/reporting/ledger/row-storage-migration.mjs +208 -0
- package/dist/lib/reporting/ledger/row-storage-object.d.mts +112 -0
- package/dist/lib/reporting/ledger/row-storage-object.d.ts +112 -0
- package/dist/lib/reporting/ledger/row-storage-object.js +304 -0
- package/dist/lib/reporting/ledger/row-storage-object.mjs +262 -0
- package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +44 -0
- package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +44 -0
- package/dist/lib/reporting/ledger/row-storage-provider-io.js +142 -0
- package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +111 -0
- package/dist/lib/reporting/ledger/row-storage.d.mts +224 -0
- package/dist/lib/reporting/ledger/row-storage.d.ts +224 -0
- package/dist/lib/reporting/ledger/row-storage.js +692 -0
- package/dist/lib/reporting/ledger/row-storage.mjs +680 -0
- package/dist/lib/reporting/ledger/types.d.mts +8 -0
- package/dist/lib/reporting/ledger/types.d.ts +8 -0
- package/dist/lib/reporting/s3/index.d.mts +29 -0
- package/dist/lib/reporting/s3/index.d.ts +29 -0
- package/dist/lib/reporting/s3/index.js +324 -0
- package/dist/lib/reporting/s3/index.mjs +317 -0
- package/dist/lib/reporting/service/index.d.mts +43 -1
- package/dist/lib/reporting/service/index.d.ts +43 -1
- package/dist/lib/reporting/service/index.js +69 -1
- package/dist/lib/reporting/service/index.mjs +71 -1
- package/dist/lib/schemas/browser.d.mts +14 -0
- package/dist/lib/schemas/browser.d.ts +14 -0
- package/dist/lib/schemas/browser.js +48 -0
- package/dist/lib/schemas/browser.mjs +20 -0
- package/dist/lib/schemas/index.d.mts +2 -120
- package/dist/lib/schemas/index.d.ts +2 -120
- package/dist/lib/schemas/index.js +3 -11
- package/dist/lib/schemas/index.mjs +1 -9
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/index.d.mts +1 -0
- package/dist/lib/testing/index.d.ts +1 -0
- package/dist/lib/testing/index.js +3 -0
- package/dist/lib/testing/index.mjs +2 -0
- package/dist/lib/testing/pending-web-flow-store.d.mts +20 -0
- package/dist/lib/testing/pending-web-flow-store.d.ts +20 -0
- package/dist/lib/testing/pending-web-flow-store.js +132 -0
- package/dist/lib/testing/pending-web-flow-store.mjs +98 -0
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/validation/get-products-request.d.mts +122 -0
- package/dist/lib/validation/get-products-request.d.ts +122 -0
- package/dist/lib/validation/get-products-request.js +39 -0
- package/dist/lib/validation/get-products-request.mjs +14 -0
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/README.md +1 -1
- package/docs/TYPE-SUMMARY.md +35 -2
- package/docs/ZOD-SCHEMAS.md +13 -1
- package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +142 -0
- package/docs/guides/EXISTING-PLATFORM.md +43 -12
- package/docs/guides/OAUTH-ISSUER-BINDING.md +6 -0
- package/docs/guides/REPORTING-LEDGER.md +374 -0
- package/docs/guides/REPORTING-OPERATIONS.md +76 -1
- package/docs/guides/WEB-OAUTH.md +48 -1
- package/docs/llms.txt +4 -2
- package/docs/migration-14.x-rc-worksheet.md +6 -4
- package/package.json +64 -1
package/dist/lib/version.js
CHANGED
|
@@ -31,7 +31,7 @@ __export(version_exports, {
|
|
|
31
31
|
toReleasePrecisionVersion: () => toReleasePrecisionVersion
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(version_exports);
|
|
34
|
-
const LIBRARY_VERSION = "15.
|
|
34
|
+
const LIBRARY_VERSION = "15.6.0";
|
|
35
35
|
const ADCP_VERSION = "3.2.3";
|
|
36
36
|
const ADCP_MAJOR_VERSION = 3;
|
|
37
37
|
const COMPATIBLE_ADCP_VERSIONS = [
|
|
@@ -97,10 +97,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
|
|
|
97
97
|
"3.2.3"
|
|
98
98
|
];
|
|
99
99
|
const VERSION_INFO = {
|
|
100
|
-
library: "15.
|
|
100
|
+
library: "15.6.0",
|
|
101
101
|
adcp: "3.2.3",
|
|
102
102
|
compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
|
|
103
|
-
generatedAt: "2026-10-
|
|
103
|
+
generatedAt: "2026-10-09T11:44:03.363Z"
|
|
104
104
|
};
|
|
105
105
|
function getAdcpVersion() {
|
|
106
106
|
return ADCP_VERSION;
|
package/dist/lib/version.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const LIBRARY_VERSION = "15.
|
|
1
|
+
const LIBRARY_VERSION = "15.6.0";
|
|
2
2
|
const ADCP_VERSION = "3.2.3";
|
|
3
3
|
const ADCP_MAJOR_VERSION = 3;
|
|
4
4
|
const COMPATIBLE_ADCP_VERSIONS = [
|
|
@@ -64,10 +64,10 @@ const COMPATIBLE_ADCP_VERSIONS = [
|
|
|
64
64
|
"3.2.3"
|
|
65
65
|
];
|
|
66
66
|
const VERSION_INFO = {
|
|
67
|
-
library: "15.
|
|
67
|
+
library: "15.6.0",
|
|
68
68
|
adcp: "3.2.3",
|
|
69
69
|
compatibleVersions: COMPATIBLE_ADCP_VERSIONS,
|
|
70
|
-
generatedAt: "2026-10-
|
|
70
|
+
generatedAt: "2026-10-09T11:44:03.363Z"
|
|
71
71
|
};
|
|
72
72
|
function getAdcpVersion() {
|
|
73
73
|
return ADCP_VERSION;
|
package/docs/README.md
CHANGED
|
@@ -23,7 +23,7 @@ list_products → buy_products → control_media_buy
|
|
|
23
23
|
|
|
24
24
|
Use the [buyer quick start](./guides/BUYER-QUICKSTART-3.2.md). For the complete
|
|
25
25
|
tool and type inventory, use [llms.txt](./llms.txt) and the
|
|
26
|
-
[type summary](./TYPE-SUMMARY.md).
|
|
26
|
+
[type summary](./TYPE-SUMMARY.md). For server-side callers that create a client per request, see [discovery caching and MCP session reuse](./guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md).
|
|
27
27
|
|
|
28
28
|
## Upgrade from SDK 14.0
|
|
29
29
|
|
package/docs/TYPE-SUMMARY.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AdCP Type Summary
|
|
2
2
|
|
|
3
|
-
> Generated at: 2026-10-
|
|
4
|
-
> @adcp/sdk v15.
|
|
3
|
+
> Generated at: 2026-10-09
|
|
4
|
+
> @adcp/sdk v15.6.0
|
|
5
5
|
|
|
6
6
|
Curated reference of the types that matter for using the AdCP client. For full generated types see `src/lib/types/tools.generated.ts` and `src/lib/types/core.generated.ts`.
|
|
7
7
|
|
|
@@ -56,6 +56,39 @@ interface TransportOptions {
|
|
|
56
56
|
legacyCompat?: A2ALegacyCompatOptions; // A2A only
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Selected transport observer fields; correlate all events by transportRequestId.
|
|
60
|
+
type TransportActivityType = 'request_started' | 'response_received' | 'response_completed' | 'request_failed';
|
|
61
|
+
type TransportResponseOutcome = 'ended' | 'errored' | 'aborted' | 'cancelled';
|
|
62
|
+
interface TransportActivity {
|
|
63
|
+
type: TransportActivityType;
|
|
64
|
+
transportRequestId: string;
|
|
65
|
+
durationMs?: number; // response_received: time to headers; response_completed: total time
|
|
66
|
+
streaming?: boolean; // Uncaptured body: terminal observation follows consumption or abort
|
|
67
|
+
outcome?: TransportResponseOutcome; // response_completed only
|
|
68
|
+
bytes?: number; // Bytes forwarded before the terminal outcome; no stream payload retained
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Share discovery across request-local clients using an application-owned authorization identity.
|
|
72
|
+
interface AgentDiscoveryCacheConfig {
|
|
73
|
+
cache: AgentDiscoveryCache; // get / set / delete; synchronous or asynchronous
|
|
74
|
+
authIdentity: string; // Trusted principal and authorization scope; never a token
|
|
75
|
+
ttlMs?: number; // Default: five minutes; maximum: 24 hours
|
|
76
|
+
}
|
|
77
|
+
const discoveryCache = createInMemoryAgentDiscoveryCache({ maxEntries: 256 });
|
|
78
|
+
const client = new SingleAgentClient(agentConfig, {
|
|
79
|
+
discoveryCache: { cache: discoveryCache, authIdentity: 'tenant:buyer-scope', ttlMs: 300_000 },
|
|
80
|
+
});
|
|
81
|
+
// Public from @adcp/sdk, @adcp/sdk/client, and @adcp/sdk/advanced.
|
|
82
|
+
await withMCPConnectionScope(async () => { await client.getCapabilities(); /* ...tool calls... */ });
|
|
83
|
+
// closeScopedConnections() can close the current workflow early.
|
|
84
|
+
interface AgentDiscoverySeed {
|
|
85
|
+
capabilities: AdcpCapabilities; // Authoritative evidence, never synthetic
|
|
86
|
+
toolSchemas?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
87
|
+
endpoint?: { agentUri: string; mcpEra?: 'legacy' | 'modern' }; // Same origin, no URL userinfo
|
|
88
|
+
}
|
|
89
|
+
// client.primeDiscoveryCache(seed): Promise<boolean>; also available on AgentClient.
|
|
90
|
+
// client.invalidateDiscoveryCache(): Promise<void>; refresh after a seller change.
|
|
91
|
+
|
|
59
92
|
interface TaskOptions {
|
|
60
93
|
skipStatusHandlers?: boolean; // Skip inline completion handlers, including continuations
|
|
61
94
|
timeout?: number; // Absolute whole-task deadline
|
package/docs/ZOD-SCHEMAS.md
CHANGED
|
@@ -20,6 +20,18 @@ if (result.success) {
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
### Browser / UI bundles
|
|
24
|
+
|
|
25
|
+
Browser widgets, dashboards, and forms should import runtime validators from `@adcp/sdk/schemas/browser`:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { EventSourceHealthSchema } from '@adcp/sdk/schemas/browser';
|
|
29
|
+
|
|
30
|
+
const health = EventSourceHealthSchema.parse(rawHealth);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This entry has no Node built-ins in its import graph and shares the Node entry's portable validators, including canonical and legacy get-products selectors. JSON Schema loading and tool registration helpers are available from `@adcp/sdk/schemas`. Importing one schema can still retain the full generated module; per-schema tree-shaking is tracked in [#3178](https://github.com/adcontextprotocol/adcp-client/issues/3178).
|
|
34
|
+
|
|
23
35
|
## Why Zod Schemas?
|
|
24
36
|
|
|
25
37
|
- ✅ **Runtime validation** - Catch data issues at runtime, not just compile time
|
|
@@ -131,7 +143,7 @@ const authority = resolvePreviewAuthority({
|
|
|
131
143
|
```typescript
|
|
132
144
|
import { useForm } from 'react-hook-form';
|
|
133
145
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
134
|
-
import { CreateMediaBuyRequestSchema } from '@adcp/sdk/schemas';
|
|
146
|
+
import { CreateMediaBuyRequestSchema } from '@adcp/sdk/schemas/browser';
|
|
135
147
|
|
|
136
148
|
function MediaBuyForm() {
|
|
137
149
|
const { register, handleSubmit } = useForm({
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Discovery cache and MCP session reuse
|
|
2
|
+
|
|
3
|
+
A server-side caller that makes one AdCP call per incoming request, building a `SingleAgentClient` (or `AgentClient`) per request because auth, headers or deadlines differ, used to pay for the SDK's whole setup sequence on every call: an endpoint probe, a `tools/list` and a `get_adcp_capabilities`, each on its own MCP session. Against a slow seller that comes straight out of the caller's deadline.
|
|
4
|
+
|
|
5
|
+
Two independent mechanisms remove that cost:
|
|
6
|
+
|
|
7
|
+
1. an **opt-in shared discovery cache** so client instances for the same agent and principal reuse one endpoint / `tools/list` / capabilities observation, and
|
|
8
|
+
2. **reusable MCP connections** that per-call correlation headers, `AbortSignal`s and timeouts no longer split, plus a public **connection scope** to run a whole workflow on one session.
|
|
9
|
+
|
|
10
|
+
## Shared discovery cache
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { AgentClient, createInMemoryAgentDiscoveryCache } from '@adcp/sdk';
|
|
14
|
+
|
|
15
|
+
// One per process (or implement AgentDiscoveryCache over your own store).
|
|
16
|
+
const discoveryCache = createInMemoryAgentDiscoveryCache({ maxEntries: 256 });
|
|
17
|
+
|
|
18
|
+
function clientFor(request: IncomingRequest) {
|
|
19
|
+
return new AgentClient(
|
|
20
|
+
{
|
|
21
|
+
id: 'seller',
|
|
22
|
+
name: 'Seller',
|
|
23
|
+
agent_uri: 'https://seller.example/mcp',
|
|
24
|
+
protocol: 'mcp',
|
|
25
|
+
auth_token: request.token,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
discoveryCache: {
|
|
29
|
+
cache: discoveryCache,
|
|
30
|
+
authIdentity: request.authorizationScopeId, // principal + tenant + granted permissions
|
|
31
|
+
ttlMs: 5 * 60_000, // default 5 minutes, max 24 hours
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### What is shared, and for how long
|
|
39
|
+
|
|
40
|
+
An entry holds the endpoint that discovery resolved, the `tools/list` input-schema map and the capabilities from an authoritative `get_adcp_capabilities` response. Every entry carries `observedAt` / `expiresAt`; the SDK enforces the TTL on read, whatever your backend does. A reader also applies its own `ttlMs`, so a short-TTL client cannot inherit a longer writer's freshness window. A long-lived client honors that bound for both endpoints and capabilities in its own in-memory copy.
|
|
41
|
+
|
|
42
|
+
### Isolation (the part to get right)
|
|
43
|
+
|
|
44
|
+
The key is a SHA-256 over:
|
|
45
|
+
|
|
46
|
+
| Input | Why |
|
|
47
|
+
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| agent URL and protocol | endpoint identity |
|
|
49
|
+
| `authIdentity` | **caller-owned** principal. Deliberately _not_ derived from the token: you decide who may share evidence |
|
|
50
|
+
| credential kind (anonymous, bearer, OAuth or header) | Keeps anonymous and authenticated discovery separate while allowing token rotation |
|
|
51
|
+
| configured `adcpVersion` / `wireAdcpVersion` | negotiated version |
|
|
52
|
+
| every non-correlation, non-credential header | unknown `x-*` tenant / routing / policy headers can select a different upstream view, so they fail closed into the key. This includes `baggage`, **ambient OpenTelemetry baggage included** |
|
|
53
|
+
| request-signing identity and policy | `kind`, `kid`, `alg`, agent URL and signing policy; never the private key |
|
|
54
|
+
| effective `allowPrivateIp`, `maxResponseBytes` | the transport policy the evidence was gathered under (per-call `transport` overrides included) |
|
|
55
|
+
|
|
56
|
+
Not part of the key: correlation headers (`x-request-id`, `x-correlation-id`, `traceparent`, ...), and credential header values (`Authorization`, `x-adcp-auth`) — OAuth and `auth_token` credentials are also owned by `authIdentity`; a token rotation for the same authorization scope keeps the entry. **Choose `authIdentity` at least as narrow as the credential's authorization scope.** Change the identity or invalidate the cache when permissions change or access is revoked; use a distinct identity for anonymous access. Two principals must never share an identity. Keys are hashes and never contain the identity, token or header values.
|
|
57
|
+
|
|
58
|
+
Never shared:
|
|
59
|
+
|
|
60
|
+
- **Synthetic capabilities** (a seller with no `get_adcp_capabilities`, or whose capabilities call failed) stay private to the instance that built them.
|
|
61
|
+
- **Scoped `trustedFetchFn` calls** bypass the cache in both directions: their network boundary is per call.
|
|
62
|
+
- Entries are copied in and out, so mutating a returned capabilities object cannot poison the cache.
|
|
63
|
+
|
|
64
|
+
### Concurrency, cancellation, failures
|
|
65
|
+
|
|
66
|
+
- Cold concurrent clients for the same key share one in-process discovery: one leader discovers, followers wait for it under **their own** signal and request timeout. A leader that is cancelled or fails never fails its followers; they discover for themselves.
|
|
67
|
+
- A cache backend that throws, returns a malformed entry or hangs costs a rediscovery, never a failed call. Each cache read or mutation wait is bounded by a 2 second deadline and, for reads and automatic writes, by the caller's `AbortSignal`. Mutations of one key are ordered in-process. A timed-out backend mutation retains its place until it settles, so a late writer cannot overwrite or delete a newer seed; later mutation waits also time out while discovery proceeds live. The queue is bounded: a write whose caller already stopped waiting is dropped if it has not started, a full per-key queue refuses further writes (a failed write, never a failed call), and invalidations queued behind a hung delete coalesce into one follow-up delete. Nothing is ever replayed automatically.
|
|
68
|
+
- The shared cache is in-process singleflight. Across processes each cold process still discovers once.
|
|
69
|
+
|
|
70
|
+
### Invalidation
|
|
71
|
+
|
|
72
|
+
- `client.invalidateDiscoveryCache()` drops this client's local evidence and the shared entry. `SingleAgentClient.refreshCapabilities()` does the same and re-discovers; on `AgentClient`, call `invalidateDiscoveryCache()` and then `getCapabilities()`.
|
|
73
|
+
- A feature check (`require`, or the pre-flight check before a task) that fails against **shared** evidence invalidates it and re-decides on live capabilities before refusing the call.
|
|
74
|
+
- A seller error of `VERSION_UNSUPPORTED`, `UNSUPPORTED_FEATURE`, `FEATURE_UNSUPPORTED`, `TOOL_NOT_FOUND`, `METHOD_NOT_FOUND` or `UNKNOWN_TOOL` invalidates the shared entry, on every task path including `executeTask()` and the typed methods. Each such live re-check costs one rediscovery; a seller that genuinely lacks a feature pays it on every refused call.
|
|
75
|
+
- A malformed or expired entry is deleted and treated as a miss. An entry older than this reader's shorter TTL is also a miss.
|
|
76
|
+
- Invalidation by any client sharing the cache in this process also drops other instances' local evidence and endpoint for that key (application-owned `primeCapabilities` evidence keeps its own freshness bound). In this process an invalidation leaves a tombstone, so a shared entry observed before it is a miss even when the backend delete failed or is still queued; the tombstone record set is bounded and, once pruned, errs toward a miss. Other processes still rely on the backend delete.
|
|
77
|
+
- In-process invalidation takes precedence over an earlier discovery still in flight. External backends with concurrent writers in other processes must supply their own atomic invalidation/versioning if they need that guarantee across processes.
|
|
78
|
+
|
|
79
|
+
### Pre-seeding
|
|
80
|
+
|
|
81
|
+
| Method | Lifetime | Result |
|
|
82
|
+
| --------------------- | -------------------------------------------------------------- | --------------------------------------------------- |
|
|
83
|
+
| `primeCapabilities` | One client, bound to its capability scope | Synchronous; `true` if accepted, otherwise `false` |
|
|
84
|
+
| `primeDiscoveryCache` | Shared cache, bound to explicit authorization identity and TTL | Asynchronous; `true` if accepted, otherwise `false` |
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const ok = await client.primeDiscoveryCache({
|
|
88
|
+
capabilities, // authoritative, non-synthetic
|
|
89
|
+
toolSchemas, // optional when capabilities.discoveredTools is present
|
|
90
|
+
endpoint: { agentUri: 'https://seller.example/mcp', mcpEra: 'modern' }, // optional
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Unlike [`primeCapabilities`](../TYPE-SUMMARY.md) — which stays client-bound and scope-keyed — seeded evidence outlives the instance. It returns `true` only once the backing cache accepted the entry, and `false` (seeding nothing) for synthetic, malformed, uncloneable or foreign-origin evidence. When the cache rejects the write or does not answer within the 2 second deadline, `false` means *not confirmed*, not *empty*: a write already handed to the backend may still land later, while one still queued behind earlier operations is dropped. After such a `false`, seed again or invalidate rather than assuming nothing was stored. Without `endpoint` the first call of a cold client still runs the endpoint probe once.
|
|
95
|
+
|
|
96
|
+
## Reusable MCP connections
|
|
97
|
+
|
|
98
|
+
This behavior applies by default, including calls that pass a signal or timeout outside a connection scope. The process-wide legacy/OAuth session cache retains at most 20 connections per cache with LRU eviction and has no idle expiry. For request workflows with many distinct credentials, use a scope to bound session lifetime. At process shutdown, `closeConnections()` from `@adcp/sdk/advanced` closes the process-wide connections.
|
|
99
|
+
|
|
100
|
+
A reusable session is keyed only by what changes the _connection_: endpoint, credential, tenant / routing headers, signing identity, `allowPrivateIp`, a caller-injected fetch, and the workflow scope. These no longer choose or split a session:
|
|
101
|
+
|
|
102
|
+
- **Correlation headers**: `traceparent`, `tracestate`, `x-request-id`, `x-correlation-id`, `x-trace-id`, `x-span-id`, `x-debug-id`, `x-client-request-id`, `x-b3-*`, `x-amzn-trace-id`, `x-cloud-trace-context`, `x-datadog-*`, `sentry-trace`, and vendor-prefixed `x-<vendor>-request-id` / `-correlation-id` / `-trace-id` / `-span-id` / `-debug-id`. Names suggesting credentials or tenant/routing identity (including `auth`, `token`, `tenant`, `account`, `workspace`, `seat`, `advertiser`, `publisher`, `buyer` and `seller`) are never treated as correlation. **`baggage` stays in the key**: it can carry tenant or routing members. Any header outside the list above also stays in the key (fail closed).
|
|
103
|
+
- **`AbortSignal`** and **`requestTimeoutMs`**: they bound a call.
|
|
104
|
+
|
|
105
|
+
Each call still sends _its own_ correlation headers, deadline and cancellation on every request over the shared session (including concurrent calls), through per-call context rather than values frozen at connect time. Explicit caller headers win over ambient OpenTelemetry context.
|
|
106
|
+
|
|
107
|
+
Isolation properties:
|
|
108
|
+
|
|
109
|
+
- A cancelled or timed-out call tears down only its own request. It does not evict or terminate a session other calls are using; other failures retire the session and close it once its in-flight calls finish. A session the seller has expired may fail the current call once; the SDK retires that connection and never replays a dispatched call, so a caller must reconcile the outcome of a mutation that failed that way (for example by re-reading it) before retrying with the same `idempotency_key`.
|
|
110
|
+
- A caller that joins another caller's in-flight connect waits under its own signal and timeout. If the creator cancels the connect, the joiner retries for itself, with at most two retries after foreign aborts.
|
|
111
|
+
- A caller-injected `fetchFn` (a per-call network trust boundary) keeps its own one-shot session outside a connection scope; inside a scope its identity is part of the key.
|
|
112
|
+
- Connections are never shared across credentials, tenant headers, signing identities or `allowPrivateIp` settings.
|
|
113
|
+
|
|
114
|
+
### Public connection scope
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { withMCPConnectionScope } from '@adcp/sdk'; // also from '@adcp/sdk/advanced'
|
|
118
|
+
|
|
119
|
+
await withMCPConnectionScope(async () => {
|
|
120
|
+
await client.syncAccounts(accounts);
|
|
121
|
+
await client.getCapabilities();
|
|
122
|
+
await client.getProducts(brief); // same MCP session
|
|
123
|
+
}); // the session is terminated once, here
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Await every operation before the scope callback returns; the callback owns the session lifetime. Open process-wide sessions can keep sockets alive, so close them at shutdown.
|
|
127
|
+
|
|
128
|
+
Inside a scope the endpoint probe, capability discovery, `tools/list` and tool calls share one negotiated session per identity. `closeScopedConnections()` closes the current MCP scope's sessions early and does nothing when no MCP scope is active (A2A connections are not scoped). Nested `withMCPConnectionScope` calls join the outer scope unless you pass `{ isolate: true }`.
|
|
129
|
+
|
|
130
|
+
### One exception: the first legacy handoff
|
|
131
|
+
|
|
132
|
+
For a seller on the pre-2026 MCP protocol era, the SDK negotiates with the v2 client first and then hands the call to the v1 client so 2025 Tasks keep working. That handoff is a cold cost the SDK cannot avoid: the first scope against such a seller opens one classification handshake plus one shared v1 session. Within the scope everything after that (probe aside) shares the v1 session, including `tools/list`, and the classification is remembered across scopes for five minutes, so a later scope opens only its own session. Modern-era sellers need a single negotiation per scope.
|
|
133
|
+
|
|
134
|
+
### Troubleshooting cache misses
|
|
135
|
+
|
|
136
|
+
Unknown headers participate in identity. A varying header such as `x-forwarded-for` causes a cache miss even when `authIdentity` matches, and so does varying `baggage`, including ambient OpenTelemetry baggage that differs per request. Ambient OpenTelemetry context is not a way to split or merge keys: set the baggage identically for callers that should share evidence, or keep it out of routing. Calls with a scoped `trustedFetchFn` bypass the cache entirely, so a client that always passes one never hits it. Also check version, transport policy, signing policy and TTL before assuming the cache backend failed.
|
|
137
|
+
|
|
138
|
+
The cache holds capabilities, schemas and the endpoint URL. Endpoint sharing excludes URLs with userinfo, a query string or a fragment; those endpoints stay local while their capabilities and schemas can still be shared. `primeDiscoveryCache` refuses such an endpoint. Put credentials in authentication headers. Treat a custom `AgentDiscoveryCache` backend as trusted storage.
|
|
139
|
+
|
|
140
|
+
## Related
|
|
141
|
+
|
|
142
|
+
- [Session reuse for multi-tool workflows](https://github.com/adcontextprotocol/adcp-client/issues/2118).
|
|
@@ -77,22 +77,53 @@ The submitted continuation's `waitForCompletion` function is deliberately proces
|
|
|
77
77
|
Do not catch every outcome into a string. Switch on `result.status`; use `result.adcpError` for failed results, and catch thrown cancellation/configuration errors separately. Internal transport retries reuse an idempotency key. A new application intent must receive a new key; after an ambiguous timeout, reconcile by the persisted natural key before deciding to retry.
|
|
78
78
|
|
|
79
79
|
Transport diagnostics never delay delivery of the operational `Response`.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
Each request emits `request_started`, then `response_received`, then (for
|
|
81
|
+
streamed replies) `response_completed`. `response_received` means **response
|
|
82
|
+
headers arrived**: its `durationMs` is time to headers. For an MCP server that
|
|
83
|
+
answers over `text/event-stream`, that is not the end of the reply, so do not
|
|
84
|
+
record it as a finished call.
|
|
85
|
+
|
|
86
|
+
Bodies are captured only when they are text-like and declare a finite
|
|
87
|
+
`Content-Length` of at most 64 KiB. Those are cloned synchronously and read in
|
|
88
|
+
the background for at most `BODY_SNIPPET_TIMEOUT_MS` (currently 1 second), and
|
|
89
|
+
their single `response_received` event is emitted when capture completes,
|
|
90
|
+
truncates, or expires. The enclosing task then waits within
|
|
88
91
|
`OBSERVER_FLUSH_TIMEOUT_MS` for that event's asynchronous observer, so
|
|
89
|
-
short-lived processes do not lose the final audit record.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
short-lived processes do not lose the final audit record.
|
|
93
|
+
|
|
94
|
+
Every other body is never cloned or buffered: SSE, non-text content, chunked
|
|
95
|
+
responses or responses with a missing or invalid `Content-Length`, and bodies
|
|
96
|
+
declared over the limit. Their `response_received` event is emitted
|
|
97
|
+
immediately with `responseBodyTruncated: true` and, when the body can be observed, `streaming: true`; `streaming` is the signal that a `response_completed` event will follow. The body
|
|
98
|
+
is passed through an observing stream with the same bytes, status, headers,
|
|
99
|
+
`url`, `redirected`, and `type`, and with pull-driven backpressure. When the
|
|
100
|
+
caller finishes with it, exactly one `response_completed` event reports
|
|
101
|
+
`outcome`, total `bytes`, and `durationMs` measured from `startedAt`:
|
|
102
|
+
|
|
103
|
+
| `outcome` | Meaning |
|
|
104
|
+
| ----------- | -------------------------------------------------------------------------- |
|
|
105
|
+
| `ended` | The caller read the stream to EOF. |
|
|
106
|
+
| `errored` | The stream failed (`errorName` / sanitized `errorMessage` are set). |
|
|
107
|
+
| `aborted` | The request's `AbortSignal` fired (also when the body is cancelled after it fired), or the stream failed with an abort error. |
|
|
108
|
+
| `cancelled` | The consumer cancelled the body, including SDK cleanup after delivering a successful MCP result. |
|
|
109
|
+
|
|
110
|
+
A streamed call that never answers therefore shows `response_received` with no
|
|
111
|
+
matching `response_completed` until the caller aborts it (for example, when its
|
|
112
|
+
deadline fires an `AbortSignal` on the request), then an `aborted` completion. `response_completed` is fire-and-forget: it can arrive
|
|
113
|
+
after the diagnostics scope exits, the scope never waits for it, and a body
|
|
114
|
+
that is never read or aborted never emits one. It carries the request correlation
|
|
115
|
+
fields, `httpStatus`, and `statusText`, but no request or response bodies and no
|
|
116
|
+
response headers. The SDK does not parse SSE messages, so it cannot say whether
|
|
117
|
+
a stream carried a final result.
|
|
118
|
+
|
|
119
|
+
Observer failures remain isolated. Applications should synchronously enqueue
|
|
120
|
+
each event into their own bounded in-memory or durable queue and return
|
|
121
|
+
promptly; flushing that queue is an application lifecycle concern.
|
|
93
122
|
|
|
94
123
|
## Reuse scoped capability evidence
|
|
95
124
|
|
|
125
|
+
For evidence shared across separate client instances, use the opt-in [discovery cache](./DISCOVERY-CACHE-AND-SESSION-REUSE.md). That guide also covers MCP connection scopes and per-call session reuse.
|
|
126
|
+
|
|
96
127
|
An application factory that already performs a bounded seller preflight can
|
|
97
128
|
construct and prime the specific client instance before exposing it for task
|
|
98
129
|
dispatch:
|
|
@@ -38,6 +38,12 @@ stage DCR privately and reconstruct for callback. Applications provide ownership
|
|
|
38
38
|
and atomic generation replacement. No distributed refresh coordination,
|
|
39
39
|
grant/client CAS or spent-grant cancellation: quiesce background callers for recovery.
|
|
40
40
|
|
|
41
|
+
Pending flows written by an older store that omitted `authorizationServerIssuer`
|
|
42
|
+
fail at callback with `oauth_issuer_required` and must restart sign-in. The SDK parser
|
|
43
|
+
can read these legacy rows, but cannot recreate their historical issuer trust.
|
|
44
|
+
Use the shared serializer and run `assertPendingWebFlowStoreRoundTrip` in CI to
|
|
45
|
+
catch dropped fields on upgrade; see [Web OAuth storage](WEB-OAUTH.md#storage-and-errors).
|
|
46
|
+
|
|
41
47
|
Custom providers must preserve issuer in getters/saves. Upstream
|
|
42
48
|
`ClientCredentialsProvider`, `PrivateKeyJwtProvider`, `StaticPrivateKeyJwtProvider`,
|
|
43
49
|
`CrossAppAccessProvider` need independent `expectedIssuer`. Direct exchange/refresh
|