@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.
Files changed (185) hide show
  1. package/dist/lib/adapters/implicit-account-store.d.mts +12 -7
  2. package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
  3. package/dist/lib/adapters/implicit-account-store.js +69 -15
  4. package/dist/lib/adapters/implicit-account-store.mjs +69 -15
  5. package/dist/lib/core/AgentClient.d.mts +1 -0
  6. package/dist/lib/core/AgentClient.d.ts +1 -0
  7. package/dist/lib/core/AgentClient.js +3 -0
  8. package/dist/lib/core/AgentClient.mjs +3 -0
  9. package/dist/lib/core/SingleAgentClient.d.mts +31 -1
  10. package/dist/lib/core/SingleAgentClient.d.ts +31 -1
  11. package/dist/lib/core/SingleAgentClient.js +355 -35
  12. package/dist/lib/core/SingleAgentClient.mjs +365 -37
  13. package/dist/lib/core/TaskExecutor.js +2 -1
  14. package/dist/lib/core/TaskExecutor.mjs +2 -1
  15. package/dist/lib/core/account-key.d.mts +3 -0
  16. package/dist/lib/core/account-key.d.ts +3 -0
  17. package/dist/lib/core/account-key.js +41 -0
  18. package/dist/lib/core/account-key.mjs +17 -0
  19. package/dist/lib/core/account-resolution.d.mts +2 -0
  20. package/dist/lib/core/account-resolution.d.ts +2 -0
  21. package/dist/lib/core/buyer-account-registry.d.mts +65 -0
  22. package/dist/lib/core/buyer-account-registry.d.ts +65 -0
  23. package/dist/lib/core/buyer-account-registry.js +518 -0
  24. package/dist/lib/core/buyer-account-registry.mjs +494 -0
  25. package/dist/lib/core/product-cache.d.mts +18 -0
  26. package/dist/lib/core/product-cache.d.ts +18 -0
  27. package/dist/lib/core/product-cache.js +137 -0
  28. package/dist/lib/core/product-cache.mjs +112 -0
  29. package/dist/lib/errors/index.d.mts +40 -1
  30. package/dist/lib/errors/index.d.ts +40 -1
  31. package/dist/lib/errors/index.js +69 -3
  32. package/dist/lib/errors/index.mjs +64 -3
  33. package/dist/lib/governance/authorization.d.mts +17 -1
  34. package/dist/lib/governance/authorization.d.ts +17 -1
  35. package/dist/lib/governance/authorization.js +55 -7
  36. package/dist/lib/governance/authorization.mjs +59 -7
  37. package/dist/lib/governance/index.d.mts +2 -2
  38. package/dist/lib/governance/index.d.ts +2 -2
  39. package/dist/lib/governance/index.js +2 -0
  40. package/dist/lib/governance/index.mjs +3 -1
  41. package/dist/lib/index.d.mts +5 -3
  42. package/dist/lib/index.d.ts +5 -3
  43. package/dist/lib/index.js +20 -0
  44. package/dist/lib/index.mjs +19 -0
  45. package/dist/lib/protocols/a2a.js +9 -1
  46. package/dist/lib/protocols/a2a.mjs +9 -1
  47. package/dist/lib/protocols/index.js +9 -2
  48. package/dist/lib/protocols/index.mjs +9 -2
  49. package/dist/lib/protocols/mcp-modern.js +2 -1
  50. package/dist/lib/protocols/mcp-modern.mjs +2 -1
  51. package/dist/lib/protocols/mcp.js +5 -2
  52. package/dist/lib/protocols/mcp.mjs +5 -2
  53. package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
  54. package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
  55. package/dist/lib/protocols/rawResponseCapture.js +41 -29
  56. package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
  57. package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
  58. package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
  59. package/dist/lib/protocols/signedRequestRejection.js +209 -0
  60. package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
  61. package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
  62. package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
  63. package/dist/lib/protocols/transportDiagnostics.js +2 -0
  64. package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
  65. package/dist/lib/registry/types.generated.d.mts +112 -45
  66. package/dist/lib/registry/types.generated.d.ts +112 -45
  67. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  68. package/dist/lib/server/account-provisioning.d.mts +2 -0
  69. package/dist/lib/server/account-provisioning.d.ts +2 -0
  70. package/dist/lib/server/account-provisioning.js +30 -0
  71. package/dist/lib/server/account-provisioning.mjs +6 -0
  72. package/dist/lib/server/account-reference-warnings.d.mts +12 -0
  73. package/dist/lib/server/account-reference-warnings.d.ts +12 -0
  74. package/dist/lib/server/account-reference-warnings.js +48 -0
  75. package/dist/lib/server/account-reference-warnings.mjs +23 -0
  76. package/dist/lib/server/auth-signature.js +1 -0
  77. package/dist/lib/server/auth-signature.mjs +1 -0
  78. package/dist/lib/server/create-adcp-server.d.mts +34 -0
  79. package/dist/lib/server/create-adcp-server.d.ts +34 -0
  80. package/dist/lib/server/create-adcp-server.js +225 -14
  81. package/dist/lib/server/create-adcp-server.mjs +225 -14
  82. package/dist/lib/server/decisioning/account.d.mts +2 -0
  83. package/dist/lib/server/decisioning/account.d.ts +2 -0
  84. package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
  85. package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
  86. package/dist/lib/server/index.d.mts +2 -2
  87. package/dist/lib/server/index.d.ts +2 -2
  88. package/dist/lib/server/index.js +2 -0
  89. package/dist/lib/server/index.mjs +3 -1
  90. package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
  91. package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
  92. package/dist/lib/signing/agent-resolver/consistency.js +0 -1
  93. package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
  94. package/dist/lib/signing/agent-resolver/errors.d.mts +1 -1
  95. package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
  96. package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
  97. package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
  98. package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
  99. package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
  100. package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
  101. package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
  102. package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
  103. package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
  104. package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
  105. package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
  106. package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
  107. package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
  108. package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
  109. package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
  110. package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
  111. package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
  112. package/dist/lib/signing/agent-resolver/resolve-agent.js +101 -132
  113. package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
  114. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
  115. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
  116. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
  117. package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
  118. package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
  119. package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
  120. package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
  121. package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
  122. package/dist/lib/signing/brand-jwks.d.mts +27 -75
  123. package/dist/lib/signing/brand-jwks.d.ts +27 -75
  124. package/dist/lib/signing/brand-jwks.js +112 -182
  125. package/dist/lib/signing/brand-jwks.mjs +112 -182
  126. package/dist/lib/signing/errors.d.mts +3 -1
  127. package/dist/lib/signing/errors.d.ts +3 -1
  128. package/dist/lib/signing/errors.js +4 -1
  129. package/dist/lib/signing/errors.mjs +4 -1
  130. package/dist/lib/signing/jwks-https.d.mts +7 -0
  131. package/dist/lib/signing/jwks-https.d.ts +7 -0
  132. package/dist/lib/signing/jwks-https.js +31 -8
  133. package/dist/lib/signing/jwks-https.mjs +31 -8
  134. package/dist/lib/signing/jwks.d.mts +8 -0
  135. package/dist/lib/signing/jwks.d.ts +8 -0
  136. package/dist/lib/signing/middleware.js +2 -1
  137. package/dist/lib/signing/middleware.mjs +2 -1
  138. package/dist/lib/signing/publisher-pins.d.mts +11 -0
  139. package/dist/lib/signing/publisher-pins.d.ts +11 -0
  140. package/dist/lib/signing/publisher-pins.js +125 -0
  141. package/dist/lib/signing/publisher-pins.mjs +101 -0
  142. package/dist/lib/signing/server.d.mts +1 -0
  143. package/dist/lib/signing/server.d.ts +1 -0
  144. package/dist/lib/signing/types.d.mts +5 -0
  145. package/dist/lib/signing/types.d.ts +5 -0
  146. package/dist/lib/signing/verifier.js +49 -4
  147. package/dist/lib/signing/verifier.mjs +49 -4
  148. package/dist/lib/signing/webhook-verifier.d.mts +7 -2
  149. package/dist/lib/signing/webhook-verifier.d.ts +7 -2
  150. package/dist/lib/signing/webhook-verifier.js +42 -2
  151. package/dist/lib/signing/webhook-verifier.mjs +43 -3
  152. package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
  153. package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
  154. package/dist/lib/testing/storyboard/account-policy.js +35 -0
  155. package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
  156. package/dist/lib/testing/storyboard/context.js +6 -0
  157. package/dist/lib/testing/storyboard/context.mjs +6 -0
  158. package/dist/lib/testing/storyboard/request-builder.js +12 -2
  159. package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
  160. package/dist/lib/testing/storyboard/runner.js +3 -2
  161. package/dist/lib/testing/storyboard/runner.mjs +3 -2
  162. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  163. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  164. package/dist/lib/version.d.mts +3 -3
  165. package/dist/lib/version.d.ts +3 -3
  166. package/dist/lib/version.js +3 -3
  167. package/dist/lib/version.mjs +3 -3
  168. package/dist/lib/wholesale-feed-sync/sync.d.mts +1 -0
  169. package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
  170. package/dist/lib/wholesale-feed-sync/sync.js +92 -21
  171. package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
  172. package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
  173. package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
  174. package/docs/TYPE-SUMMARY.md +2 -2
  175. package/docs/guides/BUILD-AN-AGENT.md +2 -2
  176. package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
  177. package/docs/guides/FIRST-CALL-TO-A-SELLER.md +104 -0
  178. package/docs/guides/SIGNING-GUIDE.md +16 -7
  179. package/docs/guides/account-resolution.md +86 -0
  180. package/docs/llms.txt +3 -2
  181. package/docs/migration-14.x-rc-worksheet.md +4 -4
  182. package/docs/migration-4.x-to-5.x.md +1 -0
  183. package/docs/migration-agent-resolution-3.3.md +123 -0
  184. package/docs/recipes/verifying-inbound-webhooks.md +56 -15
  185. 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 `BrandJsonJwksResolver` which handles this entire chain automatically, with caching and refresh.
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 { BrandJsonJwksResolver } from '@adcp/sdk/signing/server';
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 BrandJsonJwksResolver(),
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
- | `BrandJsonJwksResolver` | Full discovery chain: brand.json -> jwks_uri -> JWKS. Production recommended. |
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
- BrandJsonJwksResolver,
472
+ ResolvedAgentJwksResolver,
466
473
  InMemoryReplayStore,
474
+ InMemoryRevocationStore,
467
475
  } from '@adcp/sdk/signing/server';
468
476
 
469
- const jwks = new BrandJsonJwksResolver();
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-01
4
- > Library: @adcp/sdk v14.0.0
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.0.0` |
15
- | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@14.0.0 dist.integrity` |
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.0.0'
25
- npm view '@adcp/sdk@14.0.0' dist.integrity
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
@@ -450,6 +450,7 @@ import {
450
450
  } from '@adcp/sdk/signing/server';
451
451
 
452
452
  const jwks = new BrandJsonJwksResolver('https://publisher.example/.well-known/brand.json', {
453
+ agentUrl: 'https://publisher.example/mcp',
453
454
  agentType: 'sales',
454
455
  });
455
456
 
@@ -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 `brand.json` and selector, so a webhook for one seller cannot be
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
- BrandJsonJwksResolver,
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.brandJsonUrl}#${sender.brandId ?? '-'}#${sender.agentType}:${sender.agentId}`;
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 BrandJsonJwksResolver(sender.brandJsonUrl, {
106
+ const jwks = new ResolvedAgentJwksResolver(sender.agentUrl, sender.protocol, {
105
107
  agentType: sender.agentType,
106
108
  agentId: sender.agentId,
107
- ...(sender.brandId ? { brandId: sender.brandId } : {}),
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
- `brandJsonUrl`, optional `brandId`, and exact `webhookAuth` mode when you
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
- BrandJsonJwksResolver,
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 BrandJsonJwksResolver(sender.brandJsonUrl, {
291
+ jwks: new ResolvedAgentJwksResolver(sender.agentUrl, sender.protocol, {
253
292
  agentType: sender.agentType,
254
293
  agentId: sender.agentId,
255
- ...(sender.brandId ? { brandId: sender.brandId } : {}),
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 { BrandJsonJwksResolver, RedisReplayStore, createWebhookVerifier } from '@adcp/sdk/signing/server';
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 BrandJsonJwksResolver(sender.brandJsonUrl, {
318
+ jwks: new ResolvedAgentJwksResolver(sender.agentUrl, sender.protocol, {
279
319
  agentType: sender.agentType,
280
320
  agentId: sender.agentId,
281
- ...(sender.brandId ? { brandId: sender.brandId } : {}),
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.0.0",
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": "^2.31.1",
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",