@adcp/sdk 14.4.0 → 15.2.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/bin/adcp-grade.js +46 -2
- package/bin/adcp.js +13 -3
- package/dist/lib/auth/oauth/ClientCredentialsFlow.js +4 -1
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +4 -1
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.mts +19 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +19 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.js +95 -30
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +96 -30
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.mts +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.ts +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.js +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +1 -1
- package/dist/lib/auth/oauth/client-credentials-schema.d.mts +17 -0
- package/dist/lib/auth/oauth/client-credentials-schema.d.ts +17 -0
- package/dist/lib/auth/oauth/client-credentials-schema.js +37 -0
- package/dist/lib/auth/oauth/client-credentials-schema.mjs +13 -0
- package/dist/lib/auth/oauth/file-storage.d.mts +3 -3
- package/dist/lib/auth/oauth/file-storage.d.ts +3 -3
- package/dist/lib/auth/oauth/file-storage.js +12 -5
- package/dist/lib/auth/oauth/file-storage.mjs +12 -5
- package/dist/lib/auth/oauth/index.d.mts +10 -6
- package/dist/lib/auth/oauth/index.d.ts +10 -6
- package/dist/lib/auth/oauth/index.js +9 -3
- package/dist/lib/auth/oauth/index.mjs +8 -3
- package/dist/lib/auth/oauth/types.d.mts +9 -2
- package/dist/lib/auth/oauth/types.d.ts +9 -2
- package/dist/lib/auth/oauth/types.js +3 -2
- package/dist/lib/auth/oauth/types.mjs +3 -2
- package/dist/lib/auth/oauth/web-flow.d.mts +2 -0
- package/dist/lib/auth/oauth/web-flow.d.ts +2 -0
- package/dist/lib/auth/oauth/web-flow.js +42 -9
- package/dist/lib/auth/oauth/web-flow.mjs +42 -9
- package/dist/lib/canonical-references/index.d.mts +19 -1
- package/dist/lib/canonical-references/index.d.ts +19 -1
- package/dist/lib/canonical-references/index.js +81 -9
- package/dist/lib/canonical-references/index.mjs +80 -9
- package/dist/lib/client/core.d.mts +1 -0
- package/dist/lib/client/core.d.ts +1 -0
- package/dist/lib/core/AgentClient.d.mts +9 -1
- package/dist/lib/core/AgentClient.d.ts +9 -1
- package/dist/lib/core/AgentClient.js +10 -0
- package/dist/lib/core/AgentClient.mjs +10 -0
- package/dist/lib/core/SingleAgentClient.d.mts +13 -2
- package/dist/lib/core/SingleAgentClient.d.ts +13 -2
- package/dist/lib/core/SingleAgentClient.js +23 -5
- package/dist/lib/core/SingleAgentClient.mjs +23 -5
- package/dist/lib/index.d.mts +2 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +3 -1
- package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
- package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
- package/dist/lib/reporting/gcs/adapter.js +342 -0
- package/dist/lib/reporting/gcs/adapter.mjs +325 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
- package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
- package/dist/lib/reporting/gcs/errors.d.mts +5 -0
- package/dist/lib/reporting/gcs/errors.d.ts +5 -0
- package/dist/lib/reporting/gcs/errors.js +41 -0
- package/dist/lib/reporting/gcs/errors.mjs +16 -0
- package/dist/lib/reporting/gcs/fence.d.mts +36 -0
- package/dist/lib/reporting/gcs/fence.d.ts +36 -0
- package/dist/lib/reporting/gcs/fence.js +250 -0
- package/dist/lib/reporting/gcs/fence.mjs +230 -0
- package/dist/lib/reporting/gcs/index.d.mts +7 -0
- package/dist/lib/reporting/gcs/index.d.ts +7 -0
- package/dist/lib/reporting/gcs/index.js +41 -0
- package/dist/lib/reporting/gcs/index.mjs +12 -0
- package/dist/lib/reporting/gcs/io.d.mts +7 -0
- package/dist/lib/reporting/gcs/io.d.ts +7 -0
- package/dist/lib/reporting/gcs/io.js +95 -0
- package/dist/lib/reporting/gcs/io.mjs +69 -0
- package/dist/lib/reporting/gcs/reader.d.mts +43 -0
- package/dist/lib/reporting/gcs/reader.d.ts +43 -0
- package/dist/lib/reporting/gcs/reader.js +193 -0
- package/dist/lib/reporting/gcs/reader.mjs +169 -0
- package/dist/lib/reporting/gcs/validation.d.mts +5 -0
- package/dist/lib/reporting/gcs/validation.d.ts +5 -0
- package/dist/lib/reporting/gcs/validation.js +49 -0
- package/dist/lib/reporting/gcs/validation.mjs +25 -0
- package/dist/lib/reporting/ledger/handler.d.mts +1 -0
- package/dist/lib/reporting/ledger/handler.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.d.mts +1 -0
- package/dist/lib/reporting/ledger/index.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.js +2 -0
- package/dist/lib/reporting/ledger/index.mjs +1 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
- package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
- package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
- package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
- package/dist/lib/reporting/ledger/object-writes.js +147 -0
- package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
- package/dist/lib/schemas/index.d.mts +2 -0
- package/dist/lib/schemas/index.d.ts +2 -0
- package/dist/lib/schemas/index.js +3 -0
- package/dist/lib/schemas/index.mjs +2 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.d.mts +50 -1
- package/dist/lib/server/a2a-adapter.d.ts +50 -1
- package/dist/lib/server/a2a-adapter.js +230 -7
- package/dist/lib/server/a2a-adapter.mjs +237 -7
- package/dist/lib/server/auth-signature.d.mts +84 -46
- package/dist/lib/server/auth-signature.d.ts +84 -46
- package/dist/lib/server/auth-signature.js +48 -14
- package/dist/lib/server/auth-signature.mjs +58 -15
- package/dist/lib/server/create-adcp-server.js +5 -16
- package/dist/lib/server/create-adcp-server.mjs +5 -16
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-fetch.js +3 -33
- package/dist/lib/signing/agent-fetch.mjs +3 -33
- package/dist/lib/signing/middleware.d.mts +9 -1
- package/dist/lib/signing/middleware.d.ts +9 -1
- package/dist/lib/signing/operation-resolution.d.mts +37 -0
- package/dist/lib/signing/operation-resolution.d.ts +37 -0
- package/dist/lib/signing/operation-resolution.js +154 -0
- package/dist/lib/signing/operation-resolution.mjs +125 -0
- package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
- package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
- package/dist/lib/signing/redis-replay-store.d.mts +1 -1
- package/dist/lib/signing/redis-replay-store.d.ts +1 -1
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/server.js +7 -0
- package/dist/lib/signing/server.mjs +8 -0
- package/dist/lib/signing/verifier.d.mts +36 -1
- package/dist/lib/signing/verifier.d.ts +36 -1
- package/dist/lib/signing/verifier.js +66 -34
- package/dist/lib/signing/verifier.mjs +61 -34
- package/dist/lib/testing/client.js +8 -3
- package/dist/lib/testing/client.mjs +8 -3
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
- package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
- package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
- package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/types.d.mts +10 -1
- package/dist/lib/testing/types.d.ts +10 -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/docs/TYPE-SUMMARY.md +2 -2
- package/docs/ZOD-SCHEMAS.md +19 -0
- package/docs/guides/BUILD-AN-AGENT.md +9 -4
- package/docs/guides/CREATIVE-DELIVERY.md +34 -0
- package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
- package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
- package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
- package/docs/guides/REPORTING-OPERATIONS.md +4 -0
- package/docs/guides/SIGNING-GUIDE.md +20 -6
- package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
- package/docs/guides/WEB-OAUTH.md +58 -163
- package/docs/guides/account-resolution.md +1 -1
- package/docs/llms.txt +2 -2
- package/docs/migration-14.0-to-14.1.md +1 -1
- package/docs/migration-14.x-rc-worksheet.md +8 -7
- package/docs/migration-buyer-agent-registry.md +1 -1
- package/docs/migration-oauth-issuer-bindings.md +14 -79
- package/package.json +19 -1
- package/skills/build-seller-agent/specialisms/signed-requests.md +2 -0
package/docs/guides/WEB-OAUTH.md
CHANGED
|
@@ -1,75 +1,41 @@
|
|
|
1
|
-
# Web
|
|
1
|
+
# Web OAuth
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
`
|
|
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
|
-
##
|
|
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));
|
|
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,
|
|
61
|
-
agentStorage: pgAgentStorage,
|
|
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
|
|
89
|
-
res.redirect(
|
|
50
|
+
} catch {
|
|
51
|
+
res.redirect('/oauth-failed?reason=oauth_error');
|
|
90
52
|
}
|
|
91
53
|
});
|
|
92
54
|
```
|
|
93
55
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
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
|
-
> Generated at: 2026-10-
|
|
4
|
-
> Library: @adcp/sdk
|
|
3
|
+
> Generated at: 2026-10-07
|
|
4
|
+
> Library: @adcp/sdk v15.2.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
|
|
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
|
|
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
|
|
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@
|
|
15
|
-
| npm integrity | registry-derived after publication; run `npm view @adcp/sdk@
|
|
14
|
+
| Exact npm package | `@adcp/sdk@15.2.0` |
|
|
15
|
+
| npm integrity | registry-derived after publication; run `npm view @adcp/sdk@15.2.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@
|
|
25
|
-
npm view '@adcp/sdk@
|
|
24
|
+
npm install --save-exact '@adcp/sdk@15.2.0'
|
|
25
|
+
npm view '@adcp/sdk@15.2.0' dist.integrity
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
### Required and optional peers
|
|
@@ -32,6 +32,7 @@ Install the peer packages used by your application at versions satisfying these
|
|
|
32
32
|
| Peer | Supported range | Installation |
|
|
33
33
|
| --- | --- | --- |
|
|
34
34
|
| `@a2a-js/sdk` | `^1.0.1` | Required |
|
|
35
|
+
| `@google-cloud/storage` | `^7.22.0 || ^8.2.0` | Optional |
|
|
35
36
|
| `@modelcontextprotocol/sdk` | `^1.31.0` | Required |
|
|
36
37
|
| `@opentelemetry/api` | `^1.0.0` | Optional |
|
|
37
38
|
| `redis` | `^4.6.0 || ^5.0.0 || ^6.0.0` | Optional |
|
|
@@ -311,7 +311,7 @@ import { verifySignatureAsAuthenticator } from '@adcp/sdk/server';
|
|
|
311
311
|
const auth = verifySignatureAsAuthenticator({
|
|
312
312
|
capability,
|
|
313
313
|
jwks,
|
|
314
|
-
resolveOperation:
|
|
314
|
+
resolveOperation: adcpOperationResolver,
|
|
315
315
|
// CONFIGURE THIS: maps the verified keyid back to the agent_url it
|
|
316
316
|
// belongs to. The verifier stamps `credential.agent_url` from this
|
|
317
317
|
// lookup; without it, the http_sig credential is omitted (no
|
|
@@ -1,82 +1,17 @@
|
|
|
1
1
|
# OAuth issuer bindings
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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": "
|
|
3
|
+
"version": "15.2.0",
|
|
4
4
|
"description": "AdCP SDK — client, server, and compliance harnesses for the AdContext Protocol (MCP + A2A)",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -409,6 +409,16 @@
|
|
|
409
409
|
"types": "./dist/lib/media-buy/actions.d.ts",
|
|
410
410
|
"default": "./dist/lib/media-buy/actions.js"
|
|
411
411
|
}
|
|
412
|
+
},
|
|
413
|
+
"./reporting/gcs": {
|
|
414
|
+
"import": {
|
|
415
|
+
"types": "./dist/lib/reporting/gcs/index.d.mts",
|
|
416
|
+
"default": "./dist/lib/reporting/gcs/index.mjs"
|
|
417
|
+
},
|
|
418
|
+
"require": {
|
|
419
|
+
"types": "./dist/lib/reporting/gcs/index.d.ts",
|
|
420
|
+
"default": "./dist/lib/reporting/gcs/index.js"
|
|
421
|
+
}
|
|
412
422
|
}
|
|
413
423
|
},
|
|
414
424
|
"typesVersions": {
|
|
@@ -529,6 +539,9 @@
|
|
|
529
539
|
],
|
|
530
540
|
"media-buy/actions": [
|
|
531
541
|
"dist/lib/media-buy/actions.d.ts"
|
|
542
|
+
],
|
|
543
|
+
"reporting/gcs": [
|
|
544
|
+
"dist/lib/reporting/gcs/index.d.ts"
|
|
532
545
|
]
|
|
533
546
|
}
|
|
534
547
|
},
|
|
@@ -733,12 +746,16 @@
|
|
|
733
746
|
},
|
|
734
747
|
"peerDependencies": {
|
|
735
748
|
"@a2a-js/sdk": "^1.0.1",
|
|
749
|
+
"@google-cloud/storage": "^7.22.0 || ^8.2.0",
|
|
736
750
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
737
751
|
"@opentelemetry/api": "^1.0.0",
|
|
738
752
|
"redis": "^4.6.0 || ^5.0.0 || ^6.0.0",
|
|
739
753
|
"zod": "^4.1.5"
|
|
740
754
|
},
|
|
741
755
|
"peerDependenciesMeta": {
|
|
756
|
+
"@google-cloud/storage": {
|
|
757
|
+
"optional": true
|
|
758
|
+
},
|
|
742
759
|
"@opentelemetry/api": {
|
|
743
760
|
"optional": true
|
|
744
761
|
},
|
|
@@ -753,6 +770,7 @@
|
|
|
753
770
|
"@changesets/cli": "^3.0.3",
|
|
754
771
|
"@commitlint/cli": "^20.5.3",
|
|
755
772
|
"@commitlint/config-conventional": "^20.5.3",
|
|
773
|
+
"@google-cloud/storage": "^7.22.0",
|
|
756
774
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
757
775
|
"@opentelemetry/api": "^1.9.1",
|
|
758
776
|
"@types/express": "4.17.25",
|
|
@@ -25,6 +25,8 @@ The `WWW-Authenticate` header is the grading surface — return the right error
|
|
|
25
25
|
|
|
26
26
|
**Auto-wiring via `createAdcpServerFromPlatform`.** Pass `signedRequests: { jwks, replayStore, revocationStore }` alongside your platform and add `'signed-requests'` to `capabilities.specialisms` — the framework builds the verifier preTransport for you and `serve()` auto-mounts it. `createAdcpServerFromPlatform` throws at startup when `signedRequests` is set without the specialism claim (buyers wouldn't sign), and logs a loud error in the other direction. Keep `request_signing` in capabilities separately — it's still how buyers discover your `required_for` policy.
|
|
27
27
|
|
|
28
|
+
**A2A.** If you also mount `createA2AAdapter({ server })`, it runs the same verifier on the A2A JSON-RPC endpoint automatically, and `required_for` matches the `skill` of the Message's sole DataPart (`SendMessage`, `SendStreamingMessage`, `message/send`, `message/stream`), exactly as `params.name` does over MCP. A body that does not resolve to exactly one operation (several DataParts, a FilePart, duplicate or case-variant keys, a non-string `skill`, a batch) is rejected with `request_body_malformed` before any handler. Record the raw bytes if a body parser runs ahead of the adapter: `app.use(express.json({ verify: a2a.rawBodyVerify }))`. Since SDK 15.0.1 the adapter refuses to construct when the server advertises request signing and nothing verifies signatures on A2A; before that, A2A silently skipped `required_for` entirely (GHSA-frxv-c96c-4vqw), so upgrade if you serve A2A.
|
|
29
|
+
|
|
28
30
|
```typescript
|
|
29
31
|
const platform = definePlatform({
|
|
30
32
|
capabilities: {
|