@adcp/sdk 14.0.0 → 14.1.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/dist/lib/adapters/implicit-account-store.d.mts +12 -7
- package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
- package/dist/lib/adapters/implicit-account-store.js +69 -15
- package/dist/lib/adapters/implicit-account-store.mjs +69 -15
- package/dist/lib/core/AgentClient.d.mts +1 -0
- package/dist/lib/core/AgentClient.d.ts +1 -0
- package/dist/lib/core/AgentClient.js +3 -0
- package/dist/lib/core/AgentClient.mjs +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +31 -1
- package/dist/lib/core/SingleAgentClient.d.ts +31 -1
- package/dist/lib/core/SingleAgentClient.js +355 -35
- package/dist/lib/core/SingleAgentClient.mjs +365 -37
- package/dist/lib/core/TaskExecutor.js +2 -1
- package/dist/lib/core/TaskExecutor.mjs +2 -1
- package/dist/lib/core/account-key.d.mts +3 -0
- package/dist/lib/core/account-key.d.ts +3 -0
- package/dist/lib/core/account-key.js +41 -0
- package/dist/lib/core/account-key.mjs +17 -0
- package/dist/lib/core/account-resolution.d.mts +2 -0
- package/dist/lib/core/account-resolution.d.ts +2 -0
- package/dist/lib/core/buyer-account-registry.d.mts +65 -0
- package/dist/lib/core/buyer-account-registry.d.ts +65 -0
- package/dist/lib/core/buyer-account-registry.js +518 -0
- package/dist/lib/core/buyer-account-registry.mjs +494 -0
- package/dist/lib/core/product-cache.d.mts +18 -0
- package/dist/lib/core/product-cache.d.ts +18 -0
- package/dist/lib/core/product-cache.js +137 -0
- package/dist/lib/core/product-cache.mjs +112 -0
- package/dist/lib/errors/index.d.mts +40 -1
- package/dist/lib/errors/index.d.ts +40 -1
- package/dist/lib/errors/index.js +69 -3
- package/dist/lib/errors/index.mjs +64 -3
- package/dist/lib/governance/authorization.d.mts +17 -1
- package/dist/lib/governance/authorization.d.ts +17 -1
- package/dist/lib/governance/authorization.js +55 -7
- package/dist/lib/governance/authorization.mjs +59 -7
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +2 -0
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/index.d.mts +5 -3
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.js +20 -0
- package/dist/lib/index.mjs +19 -0
- package/dist/lib/protocols/a2a.js +9 -1
- package/dist/lib/protocols/a2a.mjs +9 -1
- package/dist/lib/protocols/index.js +9 -2
- package/dist/lib/protocols/index.mjs +9 -2
- package/dist/lib/protocols/mcp-modern.js +2 -1
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +5 -2
- package/dist/lib/protocols/mcp.mjs +5 -2
- package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
- package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
- package/dist/lib/protocols/rawResponseCapture.js +41 -29
- package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
- package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
- package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
- package/dist/lib/protocols/signedRequestRejection.js +209 -0
- package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
- package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
- package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
- package/dist/lib/protocols/transportDiagnostics.js +2 -0
- package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
- package/dist/lib/registry/types.generated.d.mts +112 -45
- package/dist/lib/registry/types.generated.d.ts +112 -45
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/account-provisioning.d.mts +2 -0
- package/dist/lib/server/account-provisioning.d.ts +2 -0
- package/dist/lib/server/account-provisioning.js +30 -0
- package/dist/lib/server/account-provisioning.mjs +6 -0
- package/dist/lib/server/account-reference-warnings.d.mts +12 -0
- package/dist/lib/server/account-reference-warnings.d.ts +12 -0
- package/dist/lib/server/account-reference-warnings.js +48 -0
- package/dist/lib/server/account-reference-warnings.mjs +23 -0
- package/dist/lib/server/auth-signature.js +1 -0
- package/dist/lib/server/auth-signature.mjs +1 -0
- package/dist/lib/server/create-adcp-server.d.mts +34 -0
- package/dist/lib/server/create-adcp-server.d.ts +34 -0
- package/dist/lib/server/create-adcp-server.js +225 -14
- package/dist/lib/server/create-adcp-server.mjs +225 -14
- package/dist/lib/server/decisioning/account.d.mts +2 -0
- package/dist/lib/server/decisioning/account.d.ts +2 -0
- package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
- package/dist/lib/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.js +0 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
- package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
- package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.js +101 -132
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
- package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
- package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
- package/dist/lib/signing/brand-jwks.d.mts +27 -75
- package/dist/lib/signing/brand-jwks.d.ts +27 -75
- package/dist/lib/signing/brand-jwks.js +112 -182
- package/dist/lib/signing/brand-jwks.mjs +112 -182
- package/dist/lib/signing/errors.d.mts +3 -1
- package/dist/lib/signing/errors.d.ts +3 -1
- package/dist/lib/signing/errors.js +4 -1
- package/dist/lib/signing/errors.mjs +4 -1
- package/dist/lib/signing/jwks-https.d.mts +7 -0
- package/dist/lib/signing/jwks-https.d.ts +7 -0
- package/dist/lib/signing/jwks-https.js +31 -8
- package/dist/lib/signing/jwks-https.mjs +31 -8
- package/dist/lib/signing/jwks.d.mts +8 -0
- package/dist/lib/signing/jwks.d.ts +8 -0
- package/dist/lib/signing/middleware.js +2 -1
- package/dist/lib/signing/middleware.mjs +2 -1
- package/dist/lib/signing/publisher-pins.d.mts +11 -0
- package/dist/lib/signing/publisher-pins.d.ts +11 -0
- package/dist/lib/signing/publisher-pins.js +125 -0
- package/dist/lib/signing/publisher-pins.mjs +101 -0
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/types.d.mts +5 -0
- package/dist/lib/signing/types.d.ts +5 -0
- package/dist/lib/signing/verifier.js +49 -4
- package/dist/lib/signing/verifier.mjs +49 -4
- package/dist/lib/signing/webhook-verifier.d.mts +7 -2
- package/dist/lib/signing/webhook-verifier.d.ts +7 -2
- package/dist/lib/signing/webhook-verifier.js +42 -2
- package/dist/lib/signing/webhook-verifier.mjs +43 -3
- package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
- package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
- package/dist/lib/testing/storyboard/account-policy.js +35 -0
- package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
- package/dist/lib/testing/storyboard/context.js +6 -0
- package/dist/lib/testing/storyboard/context.mjs +6 -0
- package/dist/lib/testing/storyboard/request-builder.js +12 -2
- package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
- package/dist/lib/testing/storyboard/runner.js +3 -2
- package/dist/lib/testing/storyboard/runner.mjs +3 -2
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- 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/dist/lib/wholesale-feed-sync/sync.d.mts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.js +92 -21
- package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
- package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
- package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +2 -2
- package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +104 -0
- package/docs/guides/SIGNING-GUIDE.md +16 -7
- package/docs/guides/account-resolution.md +86 -0
- package/docs/llms.txt +3 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-4.x-to-5.x.md +1 -0
- package/docs/migration-agent-resolution-3.3.md +123 -0
- package/docs/recipes/verifying-inbound-webhooks.md +56 -15
- package/package.json +2 -2
|
@@ -53,7 +53,7 @@ Your domain (e.g., agent.example.com)
|
|
|
53
53
|
-> /.well-known/jwks.json # JSON Web Key Set with public keys
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
`@adcp/sdk` provides `
|
|
56
|
+
`@adcp/sdk` provides `ResolvedAgentJwksResolver` which handles this entire chain automatically, with caching and refresh.
|
|
57
57
|
|
|
58
58
|
## Step 1: Generate a Signing Key
|
|
59
59
|
|
|
@@ -214,6 +214,12 @@ const signingFetch = buildAgentSigningFetch({
|
|
|
214
214
|
});
|
|
215
215
|
```
|
|
216
216
|
|
|
217
|
+
### Diagnosing a seller's rejection of a signed request
|
|
218
|
+
|
|
219
|
+
For SDK client calls over A2A or MCP, a signed HTTP 401 with a `Signature` challenge (or no challenge) produces an `AuthenticationRequiredError` with `requestSigned: true` and `status: 401`. Its `code` remains `AUTHENTICATION_REQUIRED` for compatibility; `signatureErrorCode` carries a recognized seller `request_signature_*` code, and the message includes the protocol's repair hint. Check the public discovery chain, `brand_json_url` → `agents[]` → `jwks_uri`, when the seller cannot resolve your key. Explicit Bearer or Basic gateway challenges retain their existing authentication recovery. For client-credentials agents, a bare 401 still permits one token refresh; a persistent signed rejection keeps its diagnostics. Other signed bare 401s bypass unsigned authentication probes and interactive OAuth recovery, so gateways requiring those flows should send an explicit Bearer challenge.
|
|
220
|
+
|
|
221
|
+
`error.responseBody` contains a bounded, redacted seller diagnostic when capture succeeds. It is non-enumerable and excluded from JSON error reports. Custom header values are conservatively treated as credentials; short values can mask matching diagnostic text. Treat it as untrusted operator diagnostic text; do not feed it to automated prompts or use it as recovery instructions. Failed `TaskResult` values preserve this error under `result.errorInstance`, so the diagnostic is available as `result.errorInstance.responseBody` after narrowing to `AuthenticationRequiredError`. Conformance raw capture uses the same bounded, redacted diagnostic rather than the original response bytes for these failures. Low-level signing fetch presets continue to return the original HTTP `Response`.
|
|
222
|
+
|
|
217
223
|
## Step 3.5: Production Key Storage — KMS / HSM / Vault
|
|
218
224
|
|
|
219
225
|
Holding a private JWK in process memory is fine for development and testing but it's not where you want production signing keys to live. A process compromise leaks the signing key, and the only remedy is rotation across every counterparty that's cached your public key (within their TTL). The AdCP spec recommends storing keys in a managed key store (HSM or KMS); the SDK supports this directly via the `SigningProvider` interface.
|
|
@@ -429,13 +435,13 @@ import {
|
|
|
429
435
|
requireAuthenticatedOrSigned,
|
|
430
436
|
mcpToolNameResolver,
|
|
431
437
|
} from '@adcp/sdk/server';
|
|
432
|
-
import {
|
|
438
|
+
import { ResolvedAgentJwksResolver } from '@adcp/sdk/signing/server';
|
|
433
439
|
|
|
434
440
|
serve(createAgent, {
|
|
435
441
|
authenticate: requireAuthenticatedOrSigned({
|
|
436
442
|
signature: verifySignatureAsAuthenticator({
|
|
437
443
|
capability: { supported: true, required_for: ['create_media_buy'], covers_content_digest: 'either' },
|
|
438
|
-
jwks: new
|
|
444
|
+
jwks: new ResolvedAgentJwksResolver(expectedBuyerAgentUrl, 'mcp'),
|
|
439
445
|
resolveOperation: mcpToolNameResolver,
|
|
440
446
|
}),
|
|
441
447
|
fallback: verifyApiKey({ keys: { 'sk_live_abc': { principal: 'acct_42' } } }),
|
|
@@ -453,7 +459,8 @@ Set `requiredFor` to the AdCP operations you want to gate behind signatures —
|
|
|
453
459
|
|---|---|
|
|
454
460
|
| `StaticJwksResolver` | Fixed set of known buyer keys. Good for dev/testing. |
|
|
455
461
|
| `HttpsJwksResolver` | Fetches JWKS from a URL with caching and refresh. |
|
|
456
|
-
| `
|
|
462
|
+
| `ResolvedAgentJwksResolver` | Capability-bound discovery from an expected agent URL. Production recommended. |
|
|
463
|
+
| `BrandJsonJwksResolver` | Confirms an operator mapping against capabilities; accepts an explicit `agentUrl` or infers a unique onboarding URL from existing configuration. |
|
|
457
464
|
|
|
458
465
|
## Step 5: Verify Inbound Webhooks (Buyer / Orchestrator)
|
|
459
466
|
|
|
@@ -462,16 +469,18 @@ When sellers send webhooks, verify the signature to confirm authenticity:
|
|
|
462
469
|
```typescript
|
|
463
470
|
import {
|
|
464
471
|
verifyWebhookSignature,
|
|
465
|
-
|
|
472
|
+
ResolvedAgentJwksResolver,
|
|
466
473
|
InMemoryReplayStore,
|
|
474
|
+
InMemoryRevocationStore,
|
|
467
475
|
} from '@adcp/sdk/signing/server';
|
|
468
476
|
|
|
469
|
-
const jwks = new
|
|
477
|
+
const jwks = new ResolvedAgentJwksResolver(expectedSellerAgentUrl, 'mcp', { legacyWebhookFallback: true });
|
|
470
478
|
const replayStore = new InMemoryReplayStore();
|
|
479
|
+
const revocationStore = new InMemoryRevocationStore();
|
|
471
480
|
|
|
472
481
|
app.post('/webhook', async (req, res) => {
|
|
473
482
|
try {
|
|
474
|
-
await verifyWebhookSignature(req, { jwks, replayStore });
|
|
483
|
+
await verifyWebhookSignature(req, { jwks, replayStore, revocationStore });
|
|
475
484
|
} catch (err) {
|
|
476
485
|
return res.status(401).json({ error: 'invalid webhook signature' });
|
|
477
486
|
}
|
|
@@ -24,6 +24,92 @@ accounts: {
|
|
|
24
24
|
}
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
For buyer setup and opt-in lifecycle management, see
|
|
28
|
+
[First call to a seller](./FIRST-CALL-TO-A-SELLER.md).
|
|
29
|
+
|
|
30
|
+
Account resolvers receive `ctx.provisioning`. It is false on discovery and
|
|
31
|
+
negotiation (`get_products`, `list_products`, `get_signals`, and proposal
|
|
32
|
+
request/refine/decline); these tasks MUST use lookup only. It is true on spend
|
|
33
|
+
commitments, `activate_signal`, and `sync_*` tasks. Lazy provisioning must be
|
|
34
|
+
explicitly gated:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
resolve: async (ref, ctx) => {
|
|
38
|
+
const existing = await db.findAuthorizedAccount(ref, ctx?.authInfo);
|
|
39
|
+
if (existing || !ctx?.provisioning) return existing;
|
|
40
|
+
return await db.createAuthorizedAccount(ref, ctx?.authInfo);
|
|
41
|
+
},
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When `resolveAccount` is configured, a supplied unknown reference returns
|
|
45
|
+
`ACCOUNT_NOT_FOUND`, even on optional account tools.
|
|
46
|
+
|
|
47
|
+
### Strict account references (`strictAccountReferences`)
|
|
48
|
+
|
|
49
|
+
SDK 14 keeps four compatibility behaviors that strict mode removes:
|
|
50
|
+
|
|
51
|
+
- A buyer-supplied `account` still reaches a raw handler-bag seller that has
|
|
52
|
+
no reference-aware `resolveAccount`: the handler sees `params.account` and
|
|
53
|
+
`ctx.account` is undefined. An auth-only `resolveAccountFromAuth` does not
|
|
54
|
+
authorize an arbitrary reference.
|
|
55
|
+
- A seller that declares `capabilities.account.requiredForProducts` still
|
|
56
|
+
serves `get_products` when the request carries no account and
|
|
57
|
+
authentication resolves none.
|
|
58
|
+
- `list_accounts.account` is resolved through `resolveAccount` as the
|
|
59
|
+
request's account (an unknown or unauthorized filter returns
|
|
60
|
+
`ACCOUNT_NOT_FOUND`). Strict mode treats it as a filter instead.
|
|
61
|
+
- On `createAdcpServerFromPlatform` with `resolution: 'implicit'`, an account
|
|
62
|
+
whose returned identity metadata disagrees with the supplied natural key is
|
|
63
|
+
still used.
|
|
64
|
+
|
|
65
|
+
Each logs a deprecation warning once per process per warning code (through
|
|
66
|
+
`logger.warn`, plus `process.emitWarning` outside `NODE_ENV=production`);
|
|
67
|
+
later occurrences log at debug level. Codes:
|
|
68
|
+
`ADCP_UNRESOLVED_ACCOUNT_REFERENCE`, `ADCP_REQUIRED_FOR_PRODUCTS_NOT_ENFORCED`,
|
|
69
|
+
`ADCP_LIST_ACCOUNTS_FILTER_RESOLVED`, and
|
|
70
|
+
`ADCP_IMPLICIT_ACCOUNT_IDENTITY_MISMATCH`.
|
|
71
|
+
|
|
72
|
+
**Strict mode becomes the default in the next major release.** Opt in now:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
createAdcpServer({
|
|
76
|
+
name: 'Seller',
|
|
77
|
+
version: '1.0.0',
|
|
78
|
+
strictAccountReferences: true,
|
|
79
|
+
resolveAccount: (ref, ctx) => authorizedAccounts.find(ref, ctx.authInfo),
|
|
80
|
+
mediaBuy: {
|
|
81
|
+
getProducts: (params, ctx) => catalog.forAccount(ctx.account, params),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
With `strictAccountReferences: true`:
|
|
87
|
+
|
|
88
|
+
- A supplied reference on a server without `resolveAccount` fails with
|
|
89
|
+
`ACCOUNT_NOT_FOUND` before the handler runs.
|
|
90
|
+
- A `requiredForProducts` seller refuses account-less `get_products` with
|
|
91
|
+
`ACCOUNT_REQUIRED`.
|
|
92
|
+
- `list_accounts.account` is a filter: `resolveAccount` is not called for it
|
|
93
|
+
and `ctx.account` comes from `resolveAccountFromAuth`.
|
|
94
|
+
- An implicit-mode account whose returned identity metadata (`brand`,
|
|
95
|
+
`operator`, `operator_unit`, `currency`, `timezone`, `sandbox`) disagrees
|
|
96
|
+
with the supplied natural key is refused with `ACCOUNT_NOT_FOUND`. Fields
|
|
97
|
+
your store does not return are not compared.
|
|
98
|
+
|
|
99
|
+
Migration for handler-bag sellers: move account authorization out of
|
|
100
|
+
individual handlers and into `resolveAccount`, then set the flag.
|
|
101
|
+
`authorizedAccounts.find` must return null on an unknown reference or a
|
|
102
|
+
reference owned by another principal. Public discovery may omit `account`;
|
|
103
|
+
resource creation and spend commitments need an authorized account. Implicit
|
|
104
|
+
stores should resolve by the complete natural key and return identity
|
|
105
|
+
metadata that echoes it, or omit the metadata.
|
|
106
|
+
|
|
107
|
+
`InMemoryImplicitAccountStore` matches supplied refs on the complete natural
|
|
108
|
+
key, including operator unit, currency, timezone, and sandbox. It retains its
|
|
109
|
+
24-hour TTL and replacement sync semantics. Set `mergeOnUpsert: true` for
|
|
110
|
+
additive batches and revoke individual refs with `remove(ref, ctx)`; passing
|
|
111
|
+
`delete_missing: true` in `sync_accounts` retains replacement semantics.
|
|
112
|
+
|
|
27
113
|
**Each mode has exactly one spelling.** `'derived'` keeps its name even
|
|
28
114
|
though [adcp#5062](https://github.com/adcontextprotocol/adcp/pull/5062)
|
|
29
115
|
calls the shape an *upstream-managed account-id namespace*: `resolution` is
|
package/docs/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Ad Context Protocol (AdCP)
|
|
2
2
|
|
|
3
|
-
> Generated at: 2026-10-
|
|
4
|
-
> Library: @adcp/sdk v14.
|
|
3
|
+
> Generated at: 2026-10-04
|
|
4
|
+
> Library: @adcp/sdk v14.1.0
|
|
5
5
|
> AdCP major version: 3
|
|
6
6
|
> Canonical URL: https://adcontextprotocol.github.io/adcp-client/llms.txt
|
|
7
7
|
> Note: the `Library` stamp reflects the package.json version at doc-generation time. The narrative below describes the surface that lands on the next-published minor — including any 6.7 helpers documented here ahead of the release tag.
|
|
@@ -19,6 +19,7 @@ SDK 14 is compact-lifecycle first: `list_products → buy_products → control_m
|
|
|
19
19
|
|
|
20
20
|
- **MediaBuy change rights:** use `assessMediaBuyAction` from `@adcp/sdk/media-buy/actions` for possible / promised / available-now assessment, and `mediaBuyActionResolver` from `@adcp/sdk/server` for explicit seller acceptance and current projection. See `docs/guides/MEDIA-BUY-ACTION-ASSESSMENT.md`.
|
|
21
21
|
- **Buyer** (calling a seller): read `docs/guides/BUYER-QUICKSTART-3.2.md` first.
|
|
22
|
+
- **Account setup and first discovery:** read `docs/guides/FIRST-CALL-TO-A-SELLER.md` for the provisioning registry, account policies, billing terms, and product cache.
|
|
22
23
|
- **Buyer Reliable Reporting**: import reconciliation, PostgreSQL persistence, and the worker from `@adcp/sdk/reporting/consumer`; see `docs/guides/REPORTING-RECONCILIATION.md`.
|
|
23
24
|
- **Before proposal acceptance:** use `verifyProposalCommercialTerms` from `@adcp/sdk/negotiation/verification` with a complete, independently reviewed snapshot and the seller-served schema version. Never use an unreviewed candidate as its own expected terms. See `docs/guides/PROPOSAL-TERMS-VERIFICATION.md`.
|
|
24
25
|
- **Seller** (implementing an agent that others call): read `docs/guides/SELLER-QUICKSTART-3.2.md` first, then `docs/guides/BUILD-AN-AGENT.md` for the complete framework surface.
|
|
@@ -11,8 +11,8 @@ below as the publication/deployment gate.
|
|
|
11
11
|
|
|
12
12
|
| Fact | Value |
|
|
13
13
|
| --- | --- |
|
|
14
|
-
| Exact npm package | `@adcp/sdk@14.
|
|
15
|
-
| npm integrity | registry-derived after publication; run `npm view @adcp/sdk@14.
|
|
14
|
+
| Exact npm package | `@adcp/sdk@14.1.0` |
|
|
15
|
+
| npm integrity | registry-derived after publication; run `npm view @adcp/sdk@14.1.0 dist.integrity` |
|
|
16
16
|
| Node.js runtime | `^20.19.0 || >=22.12.0` |
|
|
17
17
|
| Default AdCP wire release | `3.2.1` |
|
|
18
18
|
| Maintained wire releases | `v2.5`, `v2.6`, `v3`, `3.0.0`, `3.0`, `3.0.1`, `3.0.2`, `3.0.3`, `3.0.4`, `3.0.5`, `3.0.6`, `3.0.7`, `3.0.8`, `3.0.9`, `3.0.10`, `3.0.11`, `3.0.12`, `3.0.13`, `3.0.14`, `3.0.15`, `3.0.16`, `3.0.17`, `3.0.18`, `3.0.19`, `3.0.20`, `3.0.21`, `3.0.22`, `3.0.23`, `3.0.24`, `3.0.25`, `3.1.0`, `3.1`, `3.1.1`, `3.1.2`, `3.1.3`, `3.1.4`, `3.1.5`, `3.1.6`, `3.1.7`, `3.1.8`, `3.1.9`, `3.1.10`, `3.1.11`, `3.1.12`, `3.1.13`, `3.1.14`, `3.1.15`, `3.1.16`, `3.1.17`, `3.1.18`, `3.1.19`, `3.1.20`, `3.1.21`, `3.1.22`, `3.1.23`, `3.1.24`, `3.2.1`, `3.2` |
|
|
@@ -21,8 +21,8 @@ below as the publication/deployment gate.
|
|
|
21
21
|
Install exact production inputs rather than a moving range or dist-tag:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
npm install --save-exact '@adcp/sdk@14.
|
|
25
|
-
npm view '@adcp/sdk@14.
|
|
24
|
+
npm install --save-exact '@adcp/sdk@14.1.0'
|
|
25
|
+
npm view '@adcp/sdk@14.1.0' dist.integrity
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
### Required and optional peers
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Agent resolution and publisher pins
|
|
2
|
+
|
|
3
|
+
The SDK follows the shared AdCP 3.3 agent-resolution algorithm for request,
|
|
4
|
+
webhook and governance signatures. The protocol alignment changes key discovery
|
|
5
|
+
and verification; it does not change the SDK's configured wire protocol version.
|
|
6
|
+
|
|
7
|
+
## Expected agent URLs
|
|
8
|
+
|
|
9
|
+
Prefer passing the agent URL already recorded by your integration:
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const jwks = new ResolvedAgentJwksResolver(expectedSellerUrl, 'mcp', {
|
|
13
|
+
legacyWebhookFallback: true, // Webhooks from older 3.x sellers only.
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`BrandJsonJwksResolver(operatorUrl, { agentUrl, agentType, agentId })` remains
|
|
18
|
+
available for onboarding mappings. It confirms `operatorUrl` against the agent's
|
|
19
|
+
capabilities on cache refresh. `agentUrl` is optional for existing callers.
|
|
20
|
+
When omitted, the resolver infers one canonical URL from its trusted onboarding
|
|
21
|
+
record using the existing type/id/brand selectors, then confirms that URL's
|
|
22
|
+
capabilities-selected operator record before accepting any key. Ambiguous
|
|
23
|
+
onboarding fails closed; the inferred identity stays pinned for that resolver.
|
|
24
|
+
Moving to a different agent URL requires a new resolver instance. Explicit
|
|
25
|
+
`agentUrl` configurations must use the exact capabilities-published operator
|
|
26
|
+
URL; legacy onboarding may resolve document indirection before confirming it.
|
|
27
|
+
Existing `jwksOptions` types remain accepted; cache age and cooldown settings
|
|
28
|
+
apply within protocol bounds, and verification always fails closed on expiry.
|
|
29
|
+
`agentType` and `agentId` only narrow the final canonical URL match. Deprecated
|
|
30
|
+
`brandId` and `maxRedirects` apply only to legacy onboarding, and do not restrict
|
|
31
|
+
the final operator collections or capability discovery.
|
|
32
|
+
Explicit capability URLs allow no HTTP or document-indirection redirects. The
|
|
33
|
+
webhook-only fallback allows the bounded host/www HTTP policy and one document
|
|
34
|
+
indirection. Request verification rejects keys discovered through that fallback.
|
|
35
|
+
`SingleAgentClient` and `BrandJsonJwksResolver` enable the fallback by default for webhooks;
|
|
36
|
+
set `webhookVerification.resolverOptions.legacyWebhookFallback` to `false` to
|
|
37
|
+
disable it. Set `legacyWebhookFallback: false` on a standalone brand resolver to disable it.
|
|
38
|
+
`ResolvedAgentJwksResolver` requires explicitly enabling the fallback for webhook use.
|
|
39
|
+
Wrappers shared with request verification must forward `resolveWithMetadata`
|
|
40
|
+
so the verifier can refuse webhook-only fallback keys.
|
|
41
|
+
|
|
42
|
+
Capabilities discovery defaults to MCP. A2A integrations using
|
|
43
|
+
`BrandJsonJwksResolver` must set `protocol: 'a2a'`; the onboarding record does
|
|
44
|
+
not determine the transport. Existing cross-domain onboarding records must
|
|
45
|
+
agree with the agent's capabilities-selected operator record. A legacy
|
|
46
|
+
webhook without `brand_json_url` must use the agent-origin well-known record.
|
|
47
|
+
|
|
48
|
+
Expired operator mappings fail closed. Successful operator records have a
|
|
49
|
+
30-second minimum polling interval, including `no-cache` records, with their
|
|
50
|
+
effective cache lifetime bounded above by the JWKS revocation polling interval
|
|
51
|
+
and the configured local cap. Capabilities are re-confirmed on every refresh.
|
|
52
|
+
A local cap shorter than the discovery cooldown can temporarily reject
|
|
53
|
+
verification rather than reuse an expired mapping. Negative discovery results
|
|
54
|
+
are throttled for at most 60 seconds.
|
|
55
|
+
`maxAgeSeconds` must be positive; zero cannot provide a usable verified mapping
|
|
56
|
+
with the protocol discovery cooldown. Public `forceRefresh()` is an
|
|
57
|
+
operator-triggered cache flush and bypasses normal resolved-key cooldowns;
|
|
58
|
+
failed onboarding attempts retain their 30-second cooldown.
|
|
59
|
+
Standalone `HttpsJwksResolver` also applies its configured cooldown after a
|
|
60
|
+
failed initial fetch or refresh and rejects non-finite or negative cache
|
|
61
|
+
options. Brand resolvers validate their configuration before fetching.
|
|
62
|
+
|
|
63
|
+
Canonical identity normalization preserves path slashes, query order, trailing
|
|
64
|
+
empty queries and scheme distinctions. Update principal indexes that previously
|
|
65
|
+
used non-canonical spellings. Duplicate canonical matches within one collection
|
|
66
|
+
are ambiguous. Shared portfolio declarations count once only when their type
|
|
67
|
+
and JWKS source agree.
|
|
68
|
+
|
|
69
|
+
## Publisher pin context
|
|
70
|
+
|
|
71
|
+
Pass `publisherPins` to `verifyWebhookSignature` or `createWebhookVerifier`.
|
|
72
|
+
With the high-level client, configure `webhookVerification.publisherPins` to
|
|
73
|
+
look up those pins from the persisted registration and your own media-buy record.
|
|
74
|
+
Include every publisher whose inventory the delivery concerns. Never use payload
|
|
75
|
+
fields to choose publishers.
|
|
76
|
+
|
|
77
|
+
Each pin holds `publisher`, `signingKeys` and an async `refresh` callback that
|
|
78
|
+
bypasses the publisher's adagents.json cache. Reuse the refresh callback across
|
|
79
|
+
deliveries and bind it to the agent, publisher and tenant context it reads.
|
|
80
|
+
The SDK coalesces concurrent refreshes and reuses their confirmed result (or
|
|
81
|
+
failure) for 30 seconds, so a captured rejected delivery cannot repeatedly force
|
|
82
|
+
uncached publisher fetches. Missing `signingKeys` means no pin;
|
|
83
|
+
an empty array accepts no key. `refresh` must throw on failure; `null` means a
|
|
84
|
+
successful fetch confirmed removal of the pin. A pinned key must also appear in
|
|
85
|
+
the agent JWKS and match by RFC 7638 thumbprint. `kid`-only entries match nothing;
|
|
86
|
+
revoked entries cannot authorize delivery. `key_origins` remains enforced.
|
|
87
|
+
|
|
88
|
+
## Governance
|
|
89
|
+
|
|
90
|
+
Supply `buyerIdentity` for each authenticated request to governance verification
|
|
91
|
+
or enforcement middleware:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
await enforceGovernance({
|
|
95
|
+
...governedRequest,
|
|
96
|
+
buyerIdentity: {
|
|
97
|
+
brandJson: request.verifiedSigner.operatorRecord.document,
|
|
98
|
+
brandDomain: governedRequest.payload.brand.domain,
|
|
99
|
+
},
|
|
100
|
+
}, performGovernedAction);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For signed buyers, use the exact `operatorRecord.document` exposed by request
|
|
104
|
+
verification, the authenticator or Express middleware. Do not refetch a different
|
|
105
|
+
brand.json at its host. Other authenticated buyer identity paths may supply their
|
|
106
|
+
own trusted current record. Select the governed brand's collection, with house
|
|
107
|
+
fallback only when it has no agents override; never use a sibling brand's agents.
|
|
108
|
+
The inline brand URL hostname must equal the governed brand domain; `www` and
|
|
109
|
+
the bare domain are distinct here.
|
|
110
|
+
|
|
111
|
+
The legacy `jwks` plus `expectedIssuer` integration remains available for trusted
|
|
112
|
+
onboarding resolvers. With `buyerIdentity`, the SDK selects and caches the matched
|
|
113
|
+
entry's JWKS instead; no extra `jwks` argument is needed. Reuse a configured
|
|
114
|
+
`jwksOptions` object across requests to share its bounded resolver cache.
|
|
115
|
+
Governance replay and
|
|
116
|
+
revocation lookups use canonical issuer URLs. Migrate external indexes and replay
|
|
117
|
+
store keys when they contain non-canonical issuer spellings.
|
|
118
|
+
|
|
119
|
+
Origin binding reads `authorized_operators` only from a House Portfolio and
|
|
120
|
+
matches the exact agent eTLD+1. Brands and countries apply to account authorization,
|
|
121
|
+
separately from key discovery. Existing explicit `requiredOperatorBrand`,
|
|
122
|
+
`requiredOperatorScope` and `requiredOperatorCountry` receiver policies still
|
|
123
|
+
check the delegation tuple and its validity bounds, including at cache acceptance.
|
|
@@ -75,12 +75,12 @@ verify before parsing or re-serializing.
|
|
|
75
75
|
## Recommended RFC 9421 Setup
|
|
76
76
|
|
|
77
77
|
Create one verifier per expected sending agent. The resolver is bound to that
|
|
78
|
-
agent's
|
|
78
|
+
agent's canonical URL and protocol, so a webhook for one seller cannot be
|
|
79
79
|
verified with another seller's keys.
|
|
80
80
|
|
|
81
81
|
```ts
|
|
82
82
|
import {
|
|
83
|
-
|
|
83
|
+
ResolvedAgentJwksResolver,
|
|
84
84
|
createWebhookVerifier,
|
|
85
85
|
type BrandAgentType,
|
|
86
86
|
type RequestLike,
|
|
@@ -89,24 +89,26 @@ import {
|
|
|
89
89
|
const verifiers = new Map<string, ReturnType<typeof createWebhookVerifier>>();
|
|
90
90
|
|
|
91
91
|
type SenderRecord = {
|
|
92
|
+
operationId: string;
|
|
93
|
+
agentUrl: string;
|
|
94
|
+
protocol: 'mcp' | 'a2a';
|
|
92
95
|
agentId: string;
|
|
96
|
+
publisherPins?: readonly import('@adcp/sdk/signing/server').PublisherSigningKeyPin[];
|
|
93
97
|
agentType: BrandAgentType;
|
|
94
|
-
brandJsonUrl: string;
|
|
95
|
-
brandId?: string;
|
|
96
98
|
webhookAuth: 'rfc9421' | 'legacy-hmac';
|
|
97
99
|
legacyHmacSecret?: string;
|
|
98
100
|
};
|
|
99
101
|
|
|
100
102
|
function verifierFor(sender: SenderRecord) {
|
|
101
|
-
const cacheKey = `${sender.
|
|
103
|
+
const cacheKey = `${sender.operationId}#${sender.agentUrl}#${sender.agentType}:${sender.agentId}`;
|
|
102
104
|
let verifier = verifiers.get(cacheKey);
|
|
103
105
|
if (!verifier) {
|
|
104
|
-
const jwks = new
|
|
106
|
+
const jwks = new ResolvedAgentJwksResolver(sender.agentUrl, sender.protocol, {
|
|
105
107
|
agentType: sender.agentType,
|
|
106
108
|
agentId: sender.agentId,
|
|
107
|
-
|
|
109
|
+
legacyWebhookFallback: true,
|
|
108
110
|
});
|
|
109
|
-
verifier = createWebhookVerifier({ jwks });
|
|
111
|
+
verifier = createWebhookVerifier({ jwks, publisherPins: sender.publisherPins });
|
|
110
112
|
verifiers.set(cacheKey, verifier);
|
|
111
113
|
}
|
|
112
114
|
return verifier;
|
|
@@ -117,13 +119,50 @@ async function verifyRfc9421Webhook(sender: SenderRecord, request: RequestLike)
|
|
|
117
119
|
}
|
|
118
120
|
```
|
|
119
121
|
|
|
122
|
+
Discovery fetches the seller's `get_adcp_capabilities` through the official
|
|
123
|
+
protocol client and uses its `identity.brand_json_url`. Canonical URL matching
|
|
124
|
+
selects the agent; type and id only narrow that match. `key_origins` is checked
|
|
125
|
+
for pinned keys too. Cached mappings are re-confirmed within the brand.json
|
|
126
|
+
cache lifetime. The explicitly enabled 3.x fallback uses the agent host's
|
|
127
|
+
`/.well-known/brand.json`, then its eTLD+1 only when the host serves no record.
|
|
128
|
+
A present `brand_json_url` is always used, including when it is invalid or unreachable.
|
|
129
|
+
|
|
130
|
+
Populate `publisherPins` from your **own stored media-buy inventory**, including
|
|
131
|
+
every applicable publisher. Never choose publishers from the incoming payload.
|
|
132
|
+
A key must be published in the agent JWKS and match every applicable pin by
|
|
133
|
+
RFC 7638 thumbprint; a matching `kid` alone is insufficient. Each pin's `refresh`
|
|
134
|
+
callback must bypass the publisher's adagents.json cache and throw on failure.
|
|
135
|
+
Reuse each callback bound to its seller and tenant context across deliveries;
|
|
136
|
+
concurrent refreshes and retries share a 30-second cooldown.
|
|
137
|
+
Return `null` only when a successful authoritative fetch confirms removal of the
|
|
138
|
+
pin. Cache verifier instances per operation because different buys may have
|
|
139
|
+
different publishers, and remove them when the operation is retired.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const publisherPins = mediaBuy.publisherAuthorizations.map(authorization => ({
|
|
143
|
+
publisher: authorization.publisherDomain,
|
|
144
|
+
signingKeys: authorization.signingKeys,
|
|
145
|
+
refresh: async () => {
|
|
146
|
+
const current = await publisherStore.refreshAgentAuthorization(
|
|
147
|
+
authorization.publisherDomain, sender.agentUrl, { bypassCache: true }
|
|
148
|
+
);
|
|
149
|
+
return current.signingKeys ?? null;
|
|
150
|
+
},
|
|
151
|
+
}));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`mediaBuy` and `publisherStore` are application-owned trusted records and a
|
|
155
|
+
publisher-document fetcher. Pin misses force-refresh before final rejection,
|
|
156
|
+
after signature authentication, and fail with `webhook_signature_key_unknown`.
|
|
157
|
+
Use `onKeyResolutionError` to log the specific `request_signature_*` cause locally.
|
|
158
|
+
|
|
120
159
|
`createWebhookVerifier` defaults replay and revocation stores once at factory
|
|
121
160
|
creation time. That is safe for a single process. It is not enough behind a
|
|
122
161
|
load balancer.
|
|
123
162
|
|
|
124
163
|
`SenderRecord`, `lookupSenderForOperation()`, and `processWebhook()` are
|
|
125
164
|
application-owned. Persist the expected `agentId`, `agentType`,
|
|
126
|
-
`
|
|
165
|
+
`agentUrl`, protocol, and exact `webhookAuth` mode when you
|
|
127
166
|
initiate or register the operation. The webhook receiver should read that
|
|
128
167
|
state by operation ID before looking at any signature header.
|
|
129
168
|
|
|
@@ -234,7 +273,7 @@ Postgres:
|
|
|
234
273
|
```ts
|
|
235
274
|
import { Pool } from 'pg';
|
|
236
275
|
import {
|
|
237
|
-
|
|
276
|
+
ResolvedAgentJwksResolver,
|
|
238
277
|
PostgresReplayStore,
|
|
239
278
|
createWebhookVerifier,
|
|
240
279
|
getReplayStoreMigration,
|
|
@@ -249,12 +288,13 @@ const replayStore = new PostgresReplayStore(pool);
|
|
|
249
288
|
|
|
250
289
|
function buildVerifier(sender: SenderRecord) {
|
|
251
290
|
return createWebhookVerifier({
|
|
252
|
-
jwks: new
|
|
291
|
+
jwks: new ResolvedAgentJwksResolver(sender.agentUrl, sender.protocol, {
|
|
253
292
|
agentType: sender.agentType,
|
|
254
293
|
agentId: sender.agentId,
|
|
255
|
-
|
|
294
|
+
legacyWebhookFallback: true,
|
|
256
295
|
}),
|
|
257
296
|
replayStore,
|
|
297
|
+
publisherPins: sender.publisherPins,
|
|
258
298
|
});
|
|
259
299
|
}
|
|
260
300
|
```
|
|
@@ -263,7 +303,7 @@ Redis:
|
|
|
263
303
|
|
|
264
304
|
```ts
|
|
265
305
|
import { createClient } from 'redis';
|
|
266
|
-
import {
|
|
306
|
+
import { ResolvedAgentJwksResolver, RedisReplayStore, createWebhookVerifier } from '@adcp/sdk/signing/server';
|
|
267
307
|
|
|
268
308
|
const redis = createClient({ url: process.env.REDIS_URL });
|
|
269
309
|
await redis.connect();
|
|
@@ -275,12 +315,13 @@ const replayStore = new RedisReplayStore(redis, {
|
|
|
275
315
|
|
|
276
316
|
function buildVerifier(sender: SenderRecord) {
|
|
277
317
|
return createWebhookVerifier({
|
|
278
|
-
jwks: new
|
|
318
|
+
jwks: new ResolvedAgentJwksResolver(sender.agentUrl, sender.protocol, {
|
|
279
319
|
agentType: sender.agentType,
|
|
280
320
|
agentId: sender.agentId,
|
|
281
|
-
|
|
321
|
+
legacyWebhookFallback: true,
|
|
282
322
|
}),
|
|
283
323
|
replayStore,
|
|
324
|
+
publisherPins: sender.publisherPins,
|
|
284
325
|
});
|
|
285
326
|
}
|
|
286
327
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adcp/sdk",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.1.0",
|
|
4
4
|
"description": "AdCP SDK — client, server, and compliance harnesses for the AdContext Protocol (MCP + A2A)",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -749,7 +749,7 @@
|
|
|
749
749
|
"@a2a-js/sdk": "^1.0.1",
|
|
750
750
|
"@apidevtools/json-schema-ref-parser": "^15.5.2",
|
|
751
751
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
752
|
-
"@changesets/cli": "^
|
|
752
|
+
"@changesets/cli": "^3.0.3",
|
|
753
753
|
"@commitlint/cli": "^20.5.3",
|
|
754
754
|
"@commitlint/config-conventional": "^20.5.3",
|
|
755
755
|
"@modelcontextprotocol/sdk": "^1.30.0",
|