@adcp/sdk 14.4.0 → 15.0.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 (60) hide show
  1. package/bin/adcp.js +6 -1
  2. package/dist/lib/auth/oauth/ClientCredentialsFlow.js +4 -1
  3. package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +4 -1
  4. package/dist/lib/auth/oauth/MCPOAuthProvider.d.mts +19 -6
  5. package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +19 -6
  6. package/dist/lib/auth/oauth/MCPOAuthProvider.js +95 -30
  7. package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +96 -30
  8. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.mts +1 -1
  9. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.ts +1 -1
  10. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.js +1 -1
  11. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +1 -1
  12. package/dist/lib/auth/oauth/client-credentials-schema.d.mts +17 -0
  13. package/dist/lib/auth/oauth/client-credentials-schema.d.ts +17 -0
  14. package/dist/lib/auth/oauth/client-credentials-schema.js +37 -0
  15. package/dist/lib/auth/oauth/client-credentials-schema.mjs +13 -0
  16. package/dist/lib/auth/oauth/file-storage.d.mts +3 -3
  17. package/dist/lib/auth/oauth/file-storage.d.ts +3 -3
  18. package/dist/lib/auth/oauth/file-storage.js +12 -5
  19. package/dist/lib/auth/oauth/file-storage.mjs +12 -5
  20. package/dist/lib/auth/oauth/index.d.mts +10 -6
  21. package/dist/lib/auth/oauth/index.d.ts +10 -6
  22. package/dist/lib/auth/oauth/index.js +9 -3
  23. package/dist/lib/auth/oauth/index.mjs +8 -3
  24. package/dist/lib/auth/oauth/types.d.mts +9 -2
  25. package/dist/lib/auth/oauth/types.d.ts +9 -2
  26. package/dist/lib/auth/oauth/types.js +3 -2
  27. package/dist/lib/auth/oauth/types.mjs +3 -2
  28. package/dist/lib/auth/oauth/web-flow.d.mts +2 -0
  29. package/dist/lib/auth/oauth/web-flow.d.ts +2 -0
  30. package/dist/lib/auth/oauth/web-flow.js +42 -9
  31. package/dist/lib/auth/oauth/web-flow.mjs +42 -9
  32. package/dist/lib/index.d.mts +1 -1
  33. package/dist/lib/index.d.ts +1 -1
  34. package/dist/lib/index.js +2 -0
  35. package/dist/lib/index.mjs +3 -1
  36. package/dist/lib/schemas/index.d.mts +2 -0
  37. package/dist/lib/schemas/index.d.ts +2 -0
  38. package/dist/lib/schemas/index.js +3 -0
  39. package/dist/lib/schemas/index.mjs +2 -0
  40. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  41. package/dist/lib/testing/client.js +8 -3
  42. package/dist/lib/testing/client.mjs +8 -3
  43. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  44. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  45. package/dist/lib/testing/types.d.mts +10 -1
  46. package/dist/lib/testing/types.d.ts +10 -1
  47. package/dist/lib/version.d.mts +3 -3
  48. package/dist/lib/version.d.ts +3 -3
  49. package/dist/lib/version.js +3 -3
  50. package/dist/lib/version.mjs +3 -3
  51. package/docs/TYPE-SUMMARY.md +1 -1
  52. package/docs/ZOD-SCHEMAS.md +19 -0
  53. package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
  54. package/docs/guides/WEB-OAUTH.md +58 -163
  55. package/docs/guides/account-resolution.md +1 -1
  56. package/docs/llms.txt +1 -1
  57. package/docs/migration-14.0-to-14.1.md +1 -1
  58. package/docs/migration-14.x-rc-worksheet.md +7 -7
  59. package/docs/migration-oauth-issuer-bindings.md +14 -79
  60. package/package.json +1 -1
@@ -1,75 +1,41 @@
1
- # Web (server-side) OAuth flow
1
+ # Web OAuth
2
2
 
3
- `@adcp/sdk` provides three OAuth flow shapes:
3
+ Web routes span processes; `CLIFlowHandler` is single-process localhost:8766,
4
+ `NonInteractiveFlowHandler` is refresh-only. Web flows are not provider handlers. [Issuer binding](OAUTH-ISSUER-BINDING.md) covers recovery/storage migration.
4
5
 
5
- | Helper | Shape | When to use |
6
- | -------------------------------------------- | -------------------- | ---------------------------------------------------------------------------- |
7
- | `CLIFlowHandler` | class (`OAuthFlowHandler`) | Single-process loopback (`localhost:8766`) — CLIs and dev tools. |
8
- | `NonInteractiveFlowHandler` | class (`OAuthFlowHandler`) | Refresh-only contexts — storyboard runs, scheduled jobs, cron. |
9
- | `startWebOAuthFlow` / `completeWebOAuthFlow` | standalone functions | **Web servers** where `/oauth/start` and `/oauth/callback` may hit different processes. |
6
+ ## Discovery
10
7
 
11
- The web flow is **not** a class implementing `OAuthFlowHandler` — its
12
- contract spans two HTTP requests and (typically) two processes, which the
13
- flow-handler interface cannot model. Pass it to your router, not to
14
- `MCPOAuthProvider`.
8
+ - PRM: `/.well-known/oauth-protected-resource{path}`, first AS. Only 404 absence
9
+ (RFC 9728 §3) allows agent-origin fallback; network/parse/other HTTP failures
10
+ raise `ProtectedResourceMetadataError`.
11
+ - Resource: `resourceOverride` > validated `prm.resource` >
12
+ `resourceUrlFromServerUrl(agent.agent_uri)`. Present PRM is authoritative;
13
+ its resource origin must match the agent unless explicitly/persistently overridden. Forward
14
+ resource through authorization, exchange and refresh.
15
+ - Scope: `scopeHint` > `prm.scopes_supported` > `clientMetadata.scope`.
16
+ - DCR requires no client and an advertised `registration_endpoint`. Confidential
17
+ DCR responses require `allowConfidentialClient: true` and safe secret storage;
18
+ otherwise use pre-registered clients. Official MCP handles PKCE/exchange;
19
+ subsequent agent calls refresh.
15
20
 
16
- ## What the SDK handles
17
-
18
- 1. PRM (`/.well-known/oauth-protected-resource{path}`) discovery.
19
- 2. AS resolution: `prm.authorization_servers[0]` falling back to the agent
20
- origin when PRM is genuinely absent (404 — RFC 9728 §3 marks the
21
- metadata document as optional). Connection / parse / 5xx errors are
22
- surfaced as `ProtectedResourceMetadataError`; we do **not** silently
23
- downgrade to local guessing on a transient failure.
24
- 3. Resource indicator: caller `resourceOverride` > `prm.resource` (validated
25
- against the agent origin when no trusted override is supplied) >
26
- `resourceUrlFromServerUrl(agent.agent_uri)`. Never guessed locally
27
- when PRM is present. An explicit override is forwarded during authorization,
28
- code exchange, and later refreshes.
29
- 4. Scope: caller `scopeHint` > `prm.scopes_supported` > `clientMetadata.scope`.
30
- 5. Dynamic client registration when the agent has no `oauth_client` and
31
- the AS advertises `registration_endpoint`. By default we **reject**
32
- registrations that return `client_secret` (i.e. confidential clients);
33
- pass `allowConfidentialClient: true` if you intend to persist a
34
- long-lived AS credential to your agent storage.
35
- 6. PKCE generation, authorization URL construction, and token exchange via
36
- the MCP SDK's `client/auth.js` primitives. Refresh runs through
37
- `MCPOAuthProvider` on the next agent call and forwards `resource`
38
- automatically.
39
-
40
- ## Express integration
21
+ ## Application routes
41
22
 
42
23
  ```ts
43
- import {
44
- startWebOAuthFlow,
45
- completeWebOAuthFlow,
46
- safeReturnTo,
47
- type PendingWebFlowStore,
48
- } from '@adcp/sdk';
24
+ import { startWebOAuthFlow, completeWebOAuthFlow, safeReturnTo } from '@adcp/sdk/auth';
49
25
 
50
- // Generate a session-bound state cookie at /start, verify at /callback.
51
- // Without this, the state we store in the pending row is replay-protected
52
- // but not browser-bound — anyone with the link can finish your flow.
53
26
  const STATE_COOKIE = 'adcp_oauth_state';
54
-
55
27
  router.get('/oauth/start', async (req, res) => {
56
- const agent = await loadAgent(String(req.query.agent_id)); // adopter's agent loader
28
+ const agent = await loadAgent(String(req.query.agent_id));
57
29
  const { authorizationUrl, state } = await startWebOAuthFlow({
58
30
  agent,
59
31
  redirectUri: `${baseUrl}/oauth/callback`,
60
- pendingFlowStore: pgPendingFlowStore, // your PendingWebFlowStore
61
- agentStorage: pgAgentStorage, // your OAuthConfigStorage (optional)
32
+ pendingFlowStore: pgPendingFlowStore,
33
+ agentStorage: pgAgentStorage,
62
34
  carry: { user_id: req.user.id, return_to: req.query.return_to },
63
- // Optional: forward the scope hint from a prior 401 challenge (SEP-835).
64
- // scopeHint: req.query.scope,
65
- // Optional operator escape hatch for legacy/non-conformant integrations:
66
- // resourceOverride: agent.oauth_resource,
67
- // audience: integration.oauthAudience, // Auth0 compatibility; /authorize only
68
35
  });
69
36
  res.cookie(STATE_COOKIE, state, { httpOnly: true, secure: true, sameSite: 'lax' });
70
37
  res.redirect(authorizationUrl);
71
38
  });
72
-
73
39
  router.get('/oauth/callback', async (req, res) => {
74
40
  try {
75
41
  const { carry } = await completeWebOAuthFlow({
@@ -77,119 +43,48 @@ router.get('/oauth/callback', async (req, res) => {
77
43
  code: String(req.query.code),
78
44
  pendingFlowStore: pgPendingFlowStore,
79
45
  agentStorage: pgAgentStorage,
80
- // Browser-binding: the state we set on the cookie at /start MUST
81
- // match the state the AS sends back to /callback.
82
46
  expectedState: req.cookies[STATE_COOKIE],
83
47
  });
84
48
  res.clearCookie(STATE_COOKIE);
85
- // safeReturnTo defaults to path-only redirects; pass allowedReturnHosts
86
- // if you need to support absolute URLs against an allowlist.
87
49
  res.redirect(safeReturnTo(carry?.return_to) ?? '/');
88
- } catch (err) {
89
- res.redirect(`/oauth-failed?reason=${encodeURIComponent(err.code ?? 'oauth_error')}`);
50
+ } catch {
51
+ res.redirect('/oauth-failed?reason=oauth_error');
90
52
  }
91
53
  });
92
54
  ```
93
55
 
94
- ## What you bring
95
-
96
- - **`loadAgent(agentId)`** — your code; not provided by the SDK. The
97
- `/start` route uses it to look up the `AgentConfig`. The `/callback`
98
- route does not need it: `agentId` is round-tripped in the pending row,
99
- and `completeWebOAuthFlow` will call `agentStorage.loadAgent(agentId)`
100
- itself when persisting tokens.
101
-
102
- - **`pendingFlowStore`** — implements `PendingWebFlowStore`. Two methods:
103
- `put(flow)` and `consume(state)`. `consume` MUST be a single atomic
104
- operation; canonical implementations:
105
-
106
- Postgres:
107
-
108
- ```sql
109
- -- put
110
- INSERT INTO pending_oauth_flows (state, payload, expires_at)
111
- VALUES ($1, $2::jsonb, $3);
112
-
113
- -- consume (atomic — DELETE … RETURNING)
114
- DELETE FROM pending_oauth_flows
115
- WHERE state = $1 AND expires_at > now()
116
- RETURNING payload;
117
- ```
118
-
119
- Redis:
120
-
121
- ```
122
- SET pending:flow:<state> <payload> EX 600 NX # put
123
- GETDEL pending:flow:<state> # consume
124
- ```
125
-
126
- A `SELECT` followed by a separate `DELETE` is a replay vulnerability and
127
- DOES NOT satisfy this contract. The SDK ships a `PendingWebFlowStore`
128
- contract test (`describe('PendingWebFlowStore contract …')` in
129
- `test/lib/oauth-web-flow.test.js`) you can run against your store.
130
-
131
- - **`agentStorage`** (optional) — implements `OAuthConfigStorage` (the
132
- same interface used by `MCPOAuthProvider`). Omit it if you'd rather
133
- handle persistence yourself; `completeWebOAuthFlow` returns the issued
134
- tokens and a `persisted: false` flag in that case.
135
-
136
- `InMemoryPendingFlowStore` ships for tests and single-instance dev. Do
137
- not use it in production — restarts lose every in-flight flow.
138
-
139
- ## Errors
140
-
141
- - `InvalidOrExpiredFlowError` — `state` not present or past TTL. Treat as
142
- user-recoverable: prompt to re-authorize.
143
- - `StateMismatchError` — caller passed `expectedState` and it didn't
144
- match the AS-supplied `state`. Almost always CSRF or a stale cookie.
145
- - `BrowserBindingRequiredError` — `expectedState` was omitted. Browser callbacks
146
- fail closed by default; only non-browser compatibility flows should set
147
- `allowUnboundState: true`.
148
- - `TokenExchangeError` — AS rejected the code exchange. Carries
149
- `oauthErrorCode` (`invalid_grant`, `invalid_client`, …), `status`, and
150
- a redacted `body` for diagnostics. Treat `body` as sensitive — do not
151
- reflect it to the browser or write it to access logs unredacted.
152
- - `ProtectedResourceMetadataError` — PRM fetch failed (network / parse /
153
- non-404 HTTP) or PRM advertised a `resource` whose origin does not
154
- match the agent. Either is a configuration bug worth surfacing.
155
- - `AgentVanishedDuringFlowError` — `agentStorage.loadAgent(agentId)`
156
- returned undefined after a successful token exchange. The user's auth
157
- succeeded but you have no agent to attach it to; usually an
158
- inter-process delete race.
159
- - `AgentChangedDuringFlowError` — the agent URI or OAuth resource configuration
160
- changed while authorization was pending. Reload the current agent
161
- configuration and restart authorization; the SDK does not persist tokens
162
- against a stale or replacement agent record.
163
- - `ConfidentialClientNotAllowedError` — DCR returned a `client_secret`
164
- and you did not opt into `allowConfidentialClient: true`. Either flip
165
- the flag (and store the secret carefully) or pre-register a public
166
- client and put the result in `agent.oauth_client`.
167
- - `OAuthError` — generic catch-all; check `err.code` for the discriminator.
168
-
169
- ## Operational notes
170
-
171
- - **TTL.** Defaults to `DEFAULT_WEB_FLOW_TTL_MS` (10 minutes). Lower if
172
- you want a tighter recovery window; do not raise without a reason.
173
- - **PKCE verifier is a secret.** Encrypt it at rest if your store crosses
174
- a trust boundary.
175
- - **Atomic consume is load-bearing.** Without it, a replayed callback
176
- (e.g. user double-clicks the AS redirect) can mint two tokens and
177
- burn the authorization code on the second attempt.
178
- - **CSRF.** `expectedState` binds the flow to the user's browser. The
179
- SDK cannot do this for you because it can't see your session
180
- middleware — you stash `state` in a cookie at `/start` and pass it
181
- back in at `/callback`. Omission throws by default.
182
- - **`carry` is attacker-influenced.** It's whatever the caller of
183
- `/oauth/start` put in the request. Always validate before reflecting
184
- (use `safeReturnTo` for redirect targets).
185
- - **Refresh is not this module's job.** Once `oauth_tokens` are
186
- persisted, `MCPOAuthProvider` handles refresh on the next agent call
187
- and forwards `resource` into the refresh request automatically. An explicit
188
- `resourceOverride` is persisted as `agent.oauth_resource` when `agentStorage`
189
- is supplied so the automatic refresh path retains it.
190
- Callers who DIY refresh against `oauth_tokens` are responsible for
191
- forwarding `resource` themselves.
192
- - **Removing an override.** Pass `resourceOverride: null` to explicitly use
193
- PRM/agent-URL discovery and clear the persisted `agent.oauth_resource` after
194
- successful authorization. Omitting `resourceOverride` preserves and reuses
195
- an existing persisted override.
56
+ `loadAgent` must authorize the session user for `agent_id`. Bind browser state
57
+ using the start cookie and callback
58
+ `expectedState`: state alone prevents replay, not CSRF; missing binding refuses.
59
+ `allowUnboundState: true` is for deliberate non-browser compatibility. Validate
60
+ attacker-controlled `carry`; `safeReturnTo` is path-only unless `allowedReturnHosts`
61
+ allowlists absolute URLs.
62
+
63
+ Start accepts prior-401 `scopeHint` (SEP-835), `resourceOverride`, and authorization-only
64
+ Auth0 `audience`. With storage, overrides persist/reuse on omission; successful
65
+ `resourceOverride: null` clears them and returns to discovery. DIY refresh forwards resource.
66
+
67
+ ## Storage and errors
68
+
69
+ `PendingWebFlowStore`: `put(flow)` and atomic `consume(state)`, e.g. PostgreSQL
70
+ `DELETE ... WHERE state = $1 AND expires_at > now() RETURNING payload`, or Redis
71
+ `SET pending:flow:<state> <payload> EX 600 NX` then `GETDEL`. SELECT/DELETE races
72
+ permit replay. Store contract tests: `test/lib/oauth-web-flow.test.js`.
73
+ `DEFAULT_WEB_FLOW_TTL_MS` is 10 minutes; shorten as needed. `InMemoryPendingFlowStore` is
74
+ test/dev-only; restarts lose flows. Encrypt PKCE verifiers at rest across trust boundaries.
75
+
76
+ Optional `agentStorage` (`OAuthConfigStorage`): callback loads the pending row's
77
+ agent ID. Without it, completion
78
+ returns `persisted: false` for application persistence. Preserve the issuer guide's
79
+ consistent client-bearing view and partial-save contract.
80
+
81
+ - `InvalidOrExpiredFlowError`: restart sign-in.
82
+ - `StateMismatchError`: cookie/state mismatch.
83
+ - `BrowserBindingRequiredError`: missing `expectedState`.
84
+ - `TokenExchangeError`: diagnostic `oauthErrorCode`, `status`, redacted `body`;
85
+ never reflect bodies to browsers/logs.
86
+ - `ProtectedResourceMetadataError`: PRM/resource failure.
87
+ - `AgentVanishedDuringFlowError`: removal during registration/exchange.
88
+ - `AgentChangedDuringFlowError`: URI/resource/client changed; reload and restart.
89
+ - `ConfidentialClientNotAllowedError`: secret DCR without opt-in.
90
+ - `OAuthError`: other controlled `code` values.
@@ -71,7 +71,7 @@ later occurrences log at debug level. Codes:
71
71
  `ADCP_LIST_ACCOUNTS_FILTER_RESOLVED`, and
72
72
  `ADCP_IMPLICIT_ACCOUNT_IDENTITY_MISMATCH`.
73
73
 
74
- **Strict mode becomes the default in the next major release.** Opt in now:
74
+ **Strict mode remains an explicit opt-in in SDK 15.** Set `strictAccountReferences: true`:
75
75
 
76
76
  ```ts
77
77
  createAdcpServer({
package/docs/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ad Context Protocol (AdCP)
2
2
 
3
3
  > Generated at: 2026-10-06
4
- > Library: @adcp/sdk v14.4.0
4
+ > Library: @adcp/sdk v15.0.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.
@@ -56,7 +56,7 @@ single client-credentials refresh attempt.
56
56
  |---|---|
57
57
  | Buyer account registry | `accountPolicy` remains `'off'`. Set `'auto'` or `'strict'` to enforce setup, or configure registry storage/scope/capacity to opt into memoized `resolveAccount()`. Without opt-in, that helper still sends `sync_accounts` on each call. See [first call to a seller](./guides/FIRST-CALL-TO-A-SELLER.md). |
58
58
  | Product cache | Opt in with `createProductCache`. Public and account-scoped responses stay separate; missing `cache_scope` prevents caching. See [buyer setup and caching](./guides/FIRST-CALL-TO-A-SELLER.md). |
59
- | Strict seller account references | `strictAccountReferences` remains false; compatibility paths warn. Move reference authorization into `resolveAccount`, then set it to true. It becomes the default in the next major release. See [strict account references](./guides/account-resolution.md#strict-account-references-strictaccountreferences). |
59
+ | Strict seller account references | `strictAccountReferences` remains false by default in SDK 15; compatibility paths warn. Move reference authorization into `resolveAccount`, then set it to true to opt in. See [strict account references](./guides/account-resolution.md#strict-account-references-strictaccountreferences). |
60
60
  | Additive implicit account sync | `InMemoryImplicitAccountStore` keeps replacement sync semantics and its 24-hour TTL. Set `mergeOnUpsert: true` for additive batches; `delete_missing: true` still requests replacement. Use `remove(ref, ctx)` for individual revocation. |
61
61
 
62
62
  Account resolvers now receive `ctx.provisioning`. Discovery and negotiation
@@ -1,6 +1,6 @@
1
- # SDK 14 release-bound upgrade worksheet
1
+ # SDK release-bound upgrade worksheet
2
2
 
3
- This page joins the release facts that adopters need before changing a production pin. It is generated from package and version metadata with `npm run generate-release-worksheet`; migration decisions remain documented in [Migrating from 13.x to 14](./migration-13-to-14.md).
3
+ This page joins the release facts that adopters need before changing a production pin. It is generated from package and version metadata with `npm run generate-release-worksheet`. Before upgrading to SDK 15, review the [OAuth migration](./migration-oauth-issuer-bindings.md) and [issuer-binding and storage contract](./guides/OAUTH-ISSUER-BINDING.md). The [13.x → 14 migration](./migration-13-to-14.md) remains historical guidance.
4
4
 
5
5
  ## Release represented by this checkout
6
6
 
@@ -11,18 +11,18 @@ below as the publication/deployment gate.
11
11
 
12
12
  | Fact | Value |
13
13
  | --- | --- |
14
- | Exact npm package | `@adcp/sdk@14.4.0` |
15
- | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@14.4.0 dist.integrity` |
14
+ | Exact npm package | `@adcp/sdk@15.0.0` |
15
+ | npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.0.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` |
19
- | Canonical migration notes | [13.x → 14](./migration-13-to-14.md) |
19
+ | Canonical migration notes | [SDK 15 OAuth](./migration-oauth-issuer-bindings.md), [issuer-binding and storage](./guides/OAUTH-ISSUER-BINDING.md); historical [13.x → 14](./migration-13-to-14.md) |
20
20
 
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.4.0'
25
- npm view '@adcp/sdk@14.4.0' dist.integrity
24
+ npm install --save-exact '@adcp/sdk@15.0.0'
25
+ npm view '@adcp/sdk@15.0.0' dist.integrity
26
26
  ```
27
27
 
28
28
  ### Required and optional peers
@@ -1,82 +1,17 @@
1
1
  # OAuth issuer bindings
2
2
 
3
- The corrective release after SDK 14.3.0 upgrades the official MCP client and
4
- server to 2.2.0 and requires the legacy `@modelcontextprotocol/sdk` peer at
5
- `^1.31.0`. These versions address
3
+ SDK 14.4 upgrades MCP client/server/core to 2.2.0 and requires the legacy
4
+ `@modelcontextprotocol/sdk` peer at `^1.31.0` to address
6
5
  [GHSA-6qxp-vccf-f47h](https://github.com/advisories/GHSA-6qxp-vccf-f47h).
7
- Installations pinned to legacy SDK 1.24–1.30 must upgrade that peer dependency.
8
- The legacy OAuth transport rejects vulnerable versions even if package-manager
9
- peer checks were bypassed. The Node.js requirement remains `^20.19.0 || >=22.12.0`.
10
-
11
- OAuth refresh tokens and client secrets belong to an authorization server.
12
- `AgentOAuthTokens` and `AgentOAuthClient` now preserve an optional `issuer`
13
- field through the SDK's conversions, providers, and file storage. Newly saved
14
- credentials receive their binding from the authorization flow. The provider
15
- persists discovery alongside the pending PKCE verifier and clears that temporary
16
- state after exchange; a callback uses the original authorization server even if
17
- resource discovery changes while the browser is away. Custom `OAuthConfigStorage`
18
- implementations must preserve `oauth_discovery_state` alongside `oauth_code_verifier`. Web flows
19
- validate the discovered server's metadata issuer before registration and
20
- token exchange; diagnostic refreshes validate it before sending credentials.
21
- The provider refuses a changed authorization server before replacing a trusted
22
- confidential registration or reusing a refresh token. Clear credentials explicitly
23
- if an independently verified server migration requires a fresh registration.
24
-
25
- File storage saves the OAuth fields as an authoritative snapshot: omitted tokens,
26
- client, verifier, or discovery state are deleted. Load the complete agent from
27
- storage before creating a provider; keep unrelated persisted fields as before.
28
-
29
- ## Existing saved credentials
30
-
31
- `MCPOAuthProvider` refuses to reuse a saved refresh token or client secret
32
- without a valid issuer, with `oauth_issuer_required`. The web flow and
33
- diagnostic runner also refuse mismatched bindings. Access-token-only and
34
- public-client configurations do not require a new field. A saved unbound refresh
35
- token blocks every call through the provider, even while its access token is
36
- still valid; clear the saved credentials before reconnecting.
37
-
38
- For a saved CLI alias, run `adcp <alias> --clear-oauth`, then
39
- `adcp <alias> --oauth` to sign in again.
40
-
41
- For legacy saved credentials, clear `oauth_tokens` and any confidential
42
- `oauth_client` registration, then sign in again. Alternatively, an operator
43
- may add the authorization-server issuer established independently from
44
- trusted registration records. **Do not infer it from the current MCP server's
45
- discovery metadata**, an access token's unverified claims, or its resource URL.
46
- If the issuer is unknown, sign in again. Raw `issuer: null` is treated as absent for access-token-only and public-client
47
- configurations; secrets still require a valid string issuer.
48
-
49
- ```ts
50
- agent.oauth_tokens = {
51
- ...agent.oauth_tokens!,
52
- issuer: 'https://auth.example.com', // independently verified token issuer
53
- };
54
- agent.oauth_client = {
55
- ...agent.oauth_client!,
56
- issuer: 'https://auth.example.com', // where this client was registered
57
- };
58
- ```
59
-
60
- An issuer is an authorization-server URL, including its tenant path when one
61
- exists. A single trailing slash difference is tolerated for credential
62
- comparison; distinct tenant paths remain distinct. Stored pending web flows
63
- containing an unbound confidential client must be restarted.
64
-
65
- ## Custom providers
66
-
67
- Preserve the complete `issuer` field passed to `saveTokens()` and
68
- `saveClientInformation()`, and return it from the corresponding getters. Add
69
- an independently verified issuer to pre-registered client information. The
70
- upstream bundled `ClientCredentialsProvider`, `PrivateKeyJwtProvider`,
71
- `StaticPrivateKeyJwtProvider`, and `CrossAppAccessProvider` also need
72
- `expectedIssuer` configured. These are adopter-owned provider settings; a
73
- dependency upgrade cannot reconstruct the ownership of older credentials.
74
-
75
- Calls made directly to the upstream `exchangeAuthorization()` and
76
- `refreshAuthorization()` helpers need their own credential-issuer checks.
77
- The SDK's web-flow and diagnosis helpers perform those checks. The SDK's
78
- client-credentials flow uses an explicitly configured token endpoint rather
79
- than selecting one from MCP server discovery.
80
-
81
- If affected credentials were sent to an untrusted authorization server,
82
- follow the upstream advisory's credential-rotation guidance.
6
+ The transport rejects vulnerable legacy peers even if installation checks were bypassed.
7
+ Node requirements remain `^20.19.0 || >=22.12.0`.
8
+
9
+ SDK 15 changes direct providers to background by default (explicit
10
+ interactive opt-in). Adapters built for 14.4's authoritative-absence contract
11
+ must switch tokens, client, verifier and discovery to omission-preserves /
12
+ own-undefined-clears.
13
+
14
+ See [OAuth authorization-server binding](guides/OAUTH-ISSUER-BINDING.md) for
15
+ required owner recovery, interactive opt-in, partial-storage semantics and
16
+ web-flow migration. Rotate any credentials exposed to an untrusted server
17
+ according to the advisory.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adcp/sdk",
3
- "version": "14.4.0",
3
+ "version": "15.0.0",
4
4
  "description": "AdCP SDK — client, server, and compliance harnesses for the AdContext Protocol (MCP + A2A)",
5
5
  "workspaces": [
6
6
  ".",