@adcp/sdk 14.3.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.
- package/bin/adcp.js +8 -5
- 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 +25 -8
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +25 -8
- package/dist/lib/auth/oauth/MCPOAuthProvider.js +144 -19
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +157 -19
- 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/diagnose.js +14 -0
- package/dist/lib/auth/oauth/diagnose.mjs +14 -0
- package/dist/lib/auth/oauth/file-storage.d.mts +4 -2
- package/dist/lib/auth/oauth/file-storage.d.ts +4 -2
- package/dist/lib/auth/oauth/file-storage.js +14 -1
- package/dist/lib/auth/oauth/file-storage.mjs +14 -1
- 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 +17 -0
- package/dist/lib/auth/oauth/types.d.ts +17 -0
- package/dist/lib/auth/oauth/types.js +40 -0
- package/dist/lib/auth/oauth/types.mjs +38 -0
- 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 +48 -5
- package/dist/lib/auth/oauth/web-flow.mjs +57 -6
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +3 -1
- package/dist/lib/protocols/mcp.js +2 -0
- package/dist/lib/protocols/mcp.mjs +2 -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/testing/client.js +8 -3
- package/dist/lib/testing/client.mjs +8 -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/types/adcp.d.mts +6 -0
- package/dist/lib/types/adcp.d.ts +6 -0
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +1 -1
- package/docs/ZOD-SCHEMAS.md +19 -0
- package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
- package/docs/guides/WEB-OAUTH.md +58 -163
- package/docs/guides/account-resolution.md +1 -1
- package/docs/llms.txt +1 -1
- package/docs/migration-14.0-to-14.1.md +1 -1
- package/docs/migration-14.x-rc-worksheet.md +8 -8
- package/docs/migration-oauth-issuer-bindings.md +17 -0
- package/package.json +5 -5
|
@@ -1,6 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath as __adcpFileURLToPath } from "node:url";
|
|
2
|
+
import { dirname as __adcpDirname } from "node:path";
|
|
3
|
+
import { createRequire as __adcpCreateRequire } from "node:module";
|
|
4
|
+
const __dirname = __adcpDirname(__adcpFileURLToPath(import.meta.url));
|
|
5
|
+
const require2 = __adcpCreateRequire(import.meta.url);
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_CLIENT_METADATA,
|
|
8
|
+
toMCPTokens,
|
|
9
|
+
fromMCPTokens,
|
|
10
|
+
toMCPClientInfo,
|
|
11
|
+
fromMCPClientInfo,
|
|
12
|
+
assertOAuthCredentialIssuer,
|
|
13
|
+
assertOAuthServerIssuer,
|
|
14
|
+
OAuthError
|
|
15
|
+
} from "./types.mjs";
|
|
2
16
|
import { randomBytes } from "crypto";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { dirname, resolve } from "node:path";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
3
20
|
import { validateOAuthResourceUrl } from "./resource-url.mjs";
|
|
21
|
+
let legacyOAuthSdkChecked = false;
|
|
22
|
+
function assertLegacyOAuthSdk() {
|
|
23
|
+
if (legacyOAuthSdkChecked) return;
|
|
24
|
+
try {
|
|
25
|
+
const clientPath = createRequire(resolve(__dirname, "MCPOAuthProvider.js")).resolve(
|
|
26
|
+
"@modelcontextprotocol/sdk/client/index.js"
|
|
27
|
+
);
|
|
28
|
+
const { version } = JSON.parse(readFileSync(resolve(dirname(clientPath), "../../../package.json"), "utf8"));
|
|
29
|
+
const match = typeof version === "string" && /^1\.(\d+)\.\d+$/.exec(version);
|
|
30
|
+
if (!match || Number(match[1]) < 31) {
|
|
31
|
+
throw new OAuthError(
|
|
32
|
+
"Legacy MCP OAuth requires @modelcontextprotocol/sdk >=1.31.0 within major 1. Upgrade the installed peer dependency.",
|
|
33
|
+
"mcp_oauth_sdk_upgrade_required"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error instanceof OAuthError) throw error;
|
|
38
|
+
throw new OAuthError(
|
|
39
|
+
"Cannot verify a patched legacy MCP SDK. Install @modelcontextprotocol/sdk ^1.31.0 and keep it external when bundling.",
|
|
40
|
+
"mcp_oauth_sdk_upgrade_required"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
legacyOAuthSdkChecked = true;
|
|
44
|
+
}
|
|
4
45
|
class MCPOAuthProvider {
|
|
5
46
|
agent;
|
|
6
47
|
storage;
|
|
@@ -8,6 +49,8 @@ class MCPOAuthProvider {
|
|
|
8
49
|
_clientMetadata;
|
|
9
50
|
allowHttp;
|
|
10
51
|
configuredResourceOverride;
|
|
52
|
+
allowInteractiveAuthorization;
|
|
53
|
+
pendingDiscoveryState;
|
|
11
54
|
constructor(config) {
|
|
12
55
|
this.agent = config.agent;
|
|
13
56
|
this.storage = config.storage;
|
|
@@ -15,6 +58,7 @@ class MCPOAuthProvider {
|
|
|
15
58
|
this._clientMetadata = config.clientMetadata;
|
|
16
59
|
this.allowHttp = config.allowHttp === true;
|
|
17
60
|
this.configuredResourceOverride = config.resourceOverride;
|
|
61
|
+
this.allowInteractiveAuthorization = config.allowInteractiveAuthorization === true;
|
|
18
62
|
}
|
|
19
63
|
/**
|
|
20
64
|
* Create a provider for CLI usage
|
|
@@ -31,7 +75,8 @@ class MCPOAuthProvider {
|
|
|
31
75
|
storage,
|
|
32
76
|
clientMetadata,
|
|
33
77
|
allowHttp: options?.allowHttp,
|
|
34
|
-
resourceOverride: options?.resourceOverride
|
|
78
|
+
resourceOverride: options?.resourceOverride,
|
|
79
|
+
allowInteractiveAuthorization: true
|
|
35
80
|
});
|
|
36
81
|
}
|
|
37
82
|
// ========================================
|
|
@@ -72,8 +117,14 @@ class MCPOAuthProvider {
|
|
|
72
117
|
/**
|
|
73
118
|
* Load client information from agent config
|
|
74
119
|
*/
|
|
75
|
-
async clientInformation() {
|
|
120
|
+
async clientInformation(ctx) {
|
|
121
|
+
const issuer = this.checkIssuerContext(ctx);
|
|
122
|
+
if (issuer && !this.agent.oauth_client && !this.allowInteractiveAuthorization) {
|
|
123
|
+
throw this.ownerReauthorizationRequired();
|
|
124
|
+
}
|
|
76
125
|
if (this.agent.oauth_client) {
|
|
126
|
+
if (this.agent.oauth_client.client_secret) assertLegacyOAuthSdk();
|
|
127
|
+
assertOAuthCredentialIssuer(this.agent.oauth_client, void 0, !!this.agent.oauth_client.client_secret);
|
|
77
128
|
return toMCPClientInfo(this.agent.oauth_client);
|
|
78
129
|
}
|
|
79
130
|
return void 0;
|
|
@@ -83,13 +134,16 @@ class MCPOAuthProvider {
|
|
|
83
134
|
*/
|
|
84
135
|
async saveClientInformation(clientInfo) {
|
|
85
136
|
this.agent.oauth_client = fromMCPClientInfo(clientInfo);
|
|
86
|
-
await this.
|
|
137
|
+
await this.persistCredentials();
|
|
87
138
|
}
|
|
88
139
|
/**
|
|
89
140
|
* Load existing tokens from agent config
|
|
90
141
|
*/
|
|
91
|
-
async tokens() {
|
|
142
|
+
async tokens(ctx) {
|
|
143
|
+
this.checkIssuerContext(ctx);
|
|
92
144
|
if (this.agent.oauth_tokens) {
|
|
145
|
+
if (this.agent.oauth_tokens.refresh_token) assertLegacyOAuthSdk();
|
|
146
|
+
assertOAuthCredentialIssuer(this.agent.oauth_tokens, void 0, !!this.agent.oauth_tokens.refresh_token);
|
|
93
147
|
return toMCPTokens(this.agent.oauth_tokens);
|
|
94
148
|
}
|
|
95
149
|
return void 0;
|
|
@@ -100,19 +154,72 @@ class MCPOAuthProvider {
|
|
|
100
154
|
*/
|
|
101
155
|
async saveTokens(tokens) {
|
|
102
156
|
this.agent.oauth_tokens = fromMCPTokens(tokens);
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
if (this.allowInteractiveAuthorization) {
|
|
158
|
+
this.agent.oauth_code_verifier = void 0;
|
|
159
|
+
this.agent.oauth_discovery_state = void 0;
|
|
160
|
+
}
|
|
161
|
+
this.pendingDiscoveryState = void 0;
|
|
162
|
+
await this.persistCredentials();
|
|
105
163
|
}
|
|
106
164
|
/**
|
|
107
165
|
* Redirect user to authorization URL
|
|
108
166
|
*/
|
|
109
167
|
async redirectToAuthorization(authorizationUrl) {
|
|
110
|
-
|
|
168
|
+
if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired("interactive_required");
|
|
169
|
+
try {
|
|
170
|
+
await this.flowHandler.redirectToAuthorization(authorizationUrl);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
await this.invalidateCredentials("verifier");
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/** Persist the authorization-server identity across the callback leg. */
|
|
177
|
+
async saveDiscoveryState(state) {
|
|
178
|
+
this.validateDiscoveryState(state);
|
|
179
|
+
if (!this.agent.oauth_client && !this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
|
|
180
|
+
this.pendingDiscoveryState = structuredClone(state);
|
|
181
|
+
if (this.allowInteractiveAuthorization) {
|
|
182
|
+
this.agent.oauth_discovery_state = structuredClone(state);
|
|
183
|
+
await this.persistAgent();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async discoveryState() {
|
|
187
|
+
if (!this.allowInteractiveAuthorization) return void 0;
|
|
188
|
+
if (!this.agent.oauth_code_verifier) return void 0;
|
|
189
|
+
const state = this.pendingDiscoveryState ?? this.agent.oauth_discovery_state;
|
|
190
|
+
if (!state) return void 0;
|
|
191
|
+
this.validateDiscoveryState(state);
|
|
192
|
+
this.pendingDiscoveryState = structuredClone(state);
|
|
193
|
+
return structuredClone(state);
|
|
194
|
+
}
|
|
195
|
+
validateDiscoveryState(state) {
|
|
196
|
+
assertOAuthServerIssuer(state.authorizationServerMetadata, String(state.authorizationServerUrl));
|
|
197
|
+
this.checkIssuerContext({ issuer: String(state.authorizationServerUrl) });
|
|
198
|
+
}
|
|
199
|
+
checkIssuerContext(ctx) {
|
|
200
|
+
const issuer = ctx?.issuer ?? this.pendingDiscoveryState?.authorizationServerUrl;
|
|
201
|
+
if (issuer !== void 0) {
|
|
202
|
+
if (this.agent.oauth_tokens) {
|
|
203
|
+
assertOAuthCredentialIssuer(this.agent.oauth_tokens, issuer, !!this.agent.oauth_tokens.refresh_token);
|
|
204
|
+
}
|
|
205
|
+
if (this.agent.oauth_client) {
|
|
206
|
+
assertOAuthCredentialIssuer(this.agent.oauth_client, issuer, !!this.agent.oauth_client.client_secret);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return issuer;
|
|
210
|
+
}
|
|
211
|
+
ownerReauthorizationRequired(code = "owner_reauthorization_required") {
|
|
212
|
+
return new OAuthError(
|
|
213
|
+
"OAuth owner reauthorization is required; this provider will not automatically clear credentials or register a new client. CLI: adcp <alias> --clear-oauth, then adcp <alias> --oauth.",
|
|
214
|
+
code,
|
|
215
|
+
this.agent.id
|
|
216
|
+
);
|
|
111
217
|
}
|
|
112
218
|
/**
|
|
113
219
|
* Save PKCE code verifier
|
|
114
220
|
*/
|
|
115
221
|
async saveCodeVerifier(codeVerifier) {
|
|
222
|
+
if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
|
|
116
223
|
this.agent.oauth_code_verifier = codeVerifier;
|
|
117
224
|
await this.persistAgent();
|
|
118
225
|
}
|
|
@@ -122,6 +229,7 @@ class MCPOAuthProvider {
|
|
|
122
229
|
* retrieves it here during token exchange.
|
|
123
230
|
*/
|
|
124
231
|
async codeVerifier() {
|
|
232
|
+
if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
|
|
125
233
|
if (!this.agent.oauth_code_verifier) {
|
|
126
234
|
throw new Error(
|
|
127
235
|
"No PKCE code verifier found. The OAuth flow may have been interrupted or the agent config was modified. Please try authenticating again."
|
|
@@ -133,20 +241,40 @@ class MCPOAuthProvider {
|
|
|
133
241
|
* Invalidate credentials when server indicates they're invalid
|
|
134
242
|
*/
|
|
135
243
|
async invalidateCredentials(scope) {
|
|
244
|
+
if (!this.allowInteractiveAuthorization && (scope === "discovery" || scope === "verifier")) {
|
|
245
|
+
this.pendingDiscoveryState = void 0;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!this.allowInteractiveAuthorization && (scope === "all" || scope === "client" || scope === "tokens")) {
|
|
249
|
+
throw this.ownerReauthorizationRequired();
|
|
250
|
+
}
|
|
251
|
+
await this.clearCredentials(scope);
|
|
252
|
+
}
|
|
253
|
+
async clearCredentials(scope) {
|
|
136
254
|
switch (scope) {
|
|
137
255
|
case "all":
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
256
|
+
this.pendingDiscoveryState = void 0;
|
|
257
|
+
this.agent.oauth_discovery_state = void 0;
|
|
258
|
+
this.agent.oauth_tokens = void 0;
|
|
259
|
+
this.agent.oauth_client = void 0;
|
|
260
|
+
this.agent.oauth_code_verifier = void 0;
|
|
141
261
|
break;
|
|
142
262
|
case "tokens":
|
|
143
|
-
|
|
263
|
+
this.agent.oauth_tokens = void 0;
|
|
144
264
|
break;
|
|
145
265
|
case "client":
|
|
146
|
-
|
|
266
|
+
this.pendingDiscoveryState = void 0;
|
|
267
|
+
this.agent.oauth_discovery_state = void 0;
|
|
268
|
+
this.agent.oauth_client = void 0;
|
|
269
|
+
break;
|
|
270
|
+
case "discovery":
|
|
271
|
+
this.pendingDiscoveryState = void 0;
|
|
272
|
+
this.agent.oauth_discovery_state = void 0;
|
|
147
273
|
break;
|
|
148
274
|
case "verifier":
|
|
149
|
-
|
|
275
|
+
this.pendingDiscoveryState = void 0;
|
|
276
|
+
this.agent.oauth_discovery_state = void 0;
|
|
277
|
+
this.agent.oauth_code_verifier = void 0;
|
|
150
278
|
break;
|
|
151
279
|
}
|
|
152
280
|
await this.persistAgent();
|
|
@@ -155,11 +283,20 @@ class MCPOAuthProvider {
|
|
|
155
283
|
// Additional methods
|
|
156
284
|
// ========================================
|
|
157
285
|
/**
|
|
158
|
-
* Persist
|
|
286
|
+
* Persist a credential update without modifying independent browser state.
|
|
159
287
|
*/
|
|
160
|
-
async
|
|
288
|
+
async persistCredentials() {
|
|
289
|
+
let savedAgent = this.agent;
|
|
290
|
+
if (!this.allowInteractiveAuthorization) {
|
|
291
|
+
savedAgent = { ...this.agent };
|
|
292
|
+
delete savedAgent.oauth_code_verifier;
|
|
293
|
+
delete savedAgent.oauth_discovery_state;
|
|
294
|
+
}
|
|
295
|
+
await this.persistAgent(savedAgent);
|
|
296
|
+
}
|
|
297
|
+
async persistAgent(agent = this.agent) {
|
|
161
298
|
if (this.storage) {
|
|
162
|
-
await this.storage.saveAgent(
|
|
299
|
+
await this.storage.saveAgent(agent);
|
|
163
300
|
}
|
|
164
301
|
}
|
|
165
302
|
/**
|
|
@@ -201,7 +338,7 @@ class MCPOAuthProvider {
|
|
|
201
338
|
* Clear all OAuth data for this agent
|
|
202
339
|
*/
|
|
203
340
|
async clearAuth() {
|
|
204
|
-
await this.
|
|
341
|
+
await this.clearCredentials("all");
|
|
205
342
|
}
|
|
206
343
|
/**
|
|
207
344
|
* Get the agent config this provider manages
|
|
@@ -219,5 +356,6 @@ class MCPOAuthProvider {
|
|
|
219
356
|
}
|
|
220
357
|
}
|
|
221
358
|
export {
|
|
222
|
-
MCPOAuthProvider
|
|
359
|
+
MCPOAuthProvider,
|
|
360
|
+
assertLegacyOAuthSdk
|
|
223
361
|
};
|
|
@@ -21,7 +21,7 @@ export interface NonInteractiveFlowHandlerConfig {
|
|
|
21
21
|
redirectUrl?: string | URL;
|
|
22
22
|
/**
|
|
23
23
|
* Optional hint shown in the error message when a browser flow is
|
|
24
|
-
* attempted — e.g., the alias
|
|
24
|
+
* attempted — e.g., the saved alias for `adcp <alias> --oauth`.
|
|
25
25
|
*/
|
|
26
26
|
agentHint?: string;
|
|
27
27
|
}
|
|
@@ -21,7 +21,7 @@ export interface NonInteractiveFlowHandlerConfig {
|
|
|
21
21
|
redirectUrl?: string | URL;
|
|
22
22
|
/**
|
|
23
23
|
* Optional hint shown in the error message when a browser flow is
|
|
24
|
-
* attempted — e.g., the alias
|
|
24
|
+
* attempted — e.g., the saved alias for `adcp <alias> --oauth`.
|
|
25
25
|
*/
|
|
26
26
|
agentHint?: string;
|
|
27
27
|
}
|
|
@@ -33,7 +33,7 @@ class NonInteractiveFlowHandler {
|
|
|
33
33
|
return this._redirectUrl;
|
|
34
34
|
}
|
|
35
35
|
async redirectToAuthorization() {
|
|
36
|
-
const target = this.agentHint ? `adcp
|
|
36
|
+
const target = this.agentHint ? `adcp ${this.agentHint} --oauth` : "adcp --save-auth <alias> <url> --oauth";
|
|
37
37
|
throw new import_types.OAuthError(
|
|
38
38
|
`OAuth authorization required but this context is non-interactive. Run \`${target}\` from a terminal with a browser, then retry.`,
|
|
39
39
|
"interactive_required"
|
|
@@ -10,7 +10,7 @@ class NonInteractiveFlowHandler {
|
|
|
10
10
|
return this._redirectUrl;
|
|
11
11
|
}
|
|
12
12
|
async redirectToAuthorization() {
|
|
13
|
-
const target = this.agentHint ? `adcp
|
|
13
|
+
const target = this.agentHint ? `adcp ${this.agentHint} --oauth` : "adcp --save-auth <alias> <url> --oauth";
|
|
14
14
|
throw new OAuthError(
|
|
15
15
|
`OAuth authorization required but this context is non-interactive. Run \`${target}\` from a terminal with a browser, then retry.`,
|
|
16
16
|
"interactive_required"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* OAuth client credentials configuration shape. Secret references remain
|
|
4
|
+
* unresolved; endpoint transport and SSRF checks run during token exchange.
|
|
5
|
+
*/
|
|
6
|
+
export declare const AgentOAuthClientCredentialsSchema: z.ZodObject<{
|
|
7
|
+
token_endpoint: z.ZodString;
|
|
8
|
+
client_id: z.ZodString;
|
|
9
|
+
client_secret: z.ZodString;
|
|
10
|
+
scope: z.ZodOptional<z.ZodString>;
|
|
11
|
+
resource: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
12
|
+
audience: z.ZodOptional<z.ZodString>;
|
|
13
|
+
auth_method: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
basic: "basic";
|
|
15
|
+
body: "body";
|
|
16
|
+
}>>;
|
|
17
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* OAuth client credentials configuration shape. Secret references remain
|
|
4
|
+
* unresolved; endpoint transport and SSRF checks run during token exchange.
|
|
5
|
+
*/
|
|
6
|
+
export declare const AgentOAuthClientCredentialsSchema: z.ZodObject<{
|
|
7
|
+
token_endpoint: z.ZodString;
|
|
8
|
+
client_id: z.ZodString;
|
|
9
|
+
client_secret: z.ZodString;
|
|
10
|
+
scope: z.ZodOptional<z.ZodString>;
|
|
11
|
+
resource: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
12
|
+
audience: z.ZodOptional<z.ZodString>;
|
|
13
|
+
auth_method: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
basic: "basic";
|
|
15
|
+
body: "body";
|
|
16
|
+
}>>;
|
|
17
|
+
}, z.core.$strip>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var client_credentials_schema_exports = {};
|
|
20
|
+
__export(client_credentials_schema_exports, {
|
|
21
|
+
AgentOAuthClientCredentialsSchema: () => AgentOAuthClientCredentialsSchema
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(client_credentials_schema_exports);
|
|
24
|
+
var import_zod = require("zod");
|
|
25
|
+
const AgentOAuthClientCredentialsSchema = import_zod.z.object({
|
|
26
|
+
token_endpoint: import_zod.z.string().url(),
|
|
27
|
+
client_id: import_zod.z.string().min(1),
|
|
28
|
+
client_secret: import_zod.z.string().min(1),
|
|
29
|
+
scope: import_zod.z.string().optional(),
|
|
30
|
+
resource: import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.string())]).optional(),
|
|
31
|
+
audience: import_zod.z.string().optional(),
|
|
32
|
+
auth_method: import_zod.z.enum(["basic", "body"]).optional()
|
|
33
|
+
});
|
|
34
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
35
|
+
0 && (module.exports = {
|
|
36
|
+
AgentOAuthClientCredentialsSchema
|
|
37
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const AgentOAuthClientCredentialsSchema = z.object({
|
|
3
|
+
token_endpoint: z.string().url(),
|
|
4
|
+
client_id: z.string().min(1),
|
|
5
|
+
client_secret: z.string().min(1),
|
|
6
|
+
scope: z.string().optional(),
|
|
7
|
+
resource: z.union([z.string(), z.array(z.string())]).optional(),
|
|
8
|
+
audience: z.string().optional(),
|
|
9
|
+
auth_method: z.enum(["basic", "body"]).optional()
|
|
10
|
+
});
|
|
11
|
+
export {
|
|
12
|
+
AgentOAuthClientCredentialsSchema
|
|
13
|
+
};
|
|
@@ -23,6 +23,7 @@ __export(diagnose_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(diagnose_exports);
|
|
25
25
|
var import_net = require('../../net/index.js');
|
|
26
|
+
var import_types = require('./types.js');
|
|
26
27
|
var import_diagnostics = require('./diagnostics.js');
|
|
27
28
|
const AUTH_DIAGNOSIS_SCHEMA_VERSION = 1;
|
|
28
29
|
async function runAuthDiagnosis(agent, options = {}) {
|
|
@@ -48,6 +49,17 @@ async function runAuthDiagnosis(agent, options = {}) {
|
|
|
48
49
|
if (!options.skipRefresh && agent.oauth_tokens?.refresh_token) {
|
|
49
50
|
const tokenEndpoint = extractTokenEndpoint(asCapture);
|
|
50
51
|
const clientId = agent.oauth_client?.client_id;
|
|
52
|
+
let issuerError;
|
|
53
|
+
if (tokenEndpoint && clientId) {
|
|
54
|
+
try {
|
|
55
|
+
const issuer = new URL(extractIssuer(prmCapture) ?? "").toString();
|
|
56
|
+
(0, import_types.assertOAuthServerIssuer)(asCapture.body, issuer);
|
|
57
|
+
(0, import_types.assertOAuthCredentialIssuer)(agent.oauth_tokens, issuer, true);
|
|
58
|
+
(0, import_types.assertOAuthCredentialIssuer)(agent.oauth_client, issuer, !!agent.oauth_client?.client_secret);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
issuerError = error instanceof import_types.OAuthError ? error.message : "Authorization-server issuer could not be validated";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
51
63
|
const resource = agentUrl;
|
|
52
64
|
if (!tokenEndpoint) {
|
|
53
65
|
steps.push({
|
|
@@ -59,6 +71,8 @@ async function runAuthDiagnosis(agent, options = {}) {
|
|
|
59
71
|
name: "token_refresh_attempt",
|
|
60
72
|
error: "No saved oauth_client.client_id \u2014 cannot attempt refresh without it"
|
|
61
73
|
});
|
|
74
|
+
} else if (issuerError) {
|
|
75
|
+
steps.push({ name: "token_refresh_attempt", error: issuerError });
|
|
62
76
|
} else {
|
|
63
77
|
const refreshCapture = await attemptTokenRefresh({
|
|
64
78
|
tokenEndpoint,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ssrfSafeFetch, decodeBodyAsJsonOrText, SsrfRefusedError } from "../../net/index.mjs";
|
|
2
|
+
import { assertOAuthCredentialIssuer, assertOAuthServerIssuer, OAuthError } from "./types.mjs";
|
|
2
3
|
import { decodeAccessTokenClaims, parseWWWAuthenticate, validateTokenAudience } from "./diagnostics.mjs";
|
|
3
4
|
const AUTH_DIAGNOSIS_SCHEMA_VERSION = 1;
|
|
4
5
|
async function runAuthDiagnosis(agent, options = {}) {
|
|
@@ -24,6 +25,17 @@ async function runAuthDiagnosis(agent, options = {}) {
|
|
|
24
25
|
if (!options.skipRefresh && agent.oauth_tokens?.refresh_token) {
|
|
25
26
|
const tokenEndpoint = extractTokenEndpoint(asCapture);
|
|
26
27
|
const clientId = agent.oauth_client?.client_id;
|
|
28
|
+
let issuerError;
|
|
29
|
+
if (tokenEndpoint && clientId) {
|
|
30
|
+
try {
|
|
31
|
+
const issuer = new URL(extractIssuer(prmCapture) ?? "").toString();
|
|
32
|
+
assertOAuthServerIssuer(asCapture.body, issuer);
|
|
33
|
+
assertOAuthCredentialIssuer(agent.oauth_tokens, issuer, true);
|
|
34
|
+
assertOAuthCredentialIssuer(agent.oauth_client, issuer, !!agent.oauth_client?.client_secret);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
issuerError = error instanceof OAuthError ? error.message : "Authorization-server issuer could not be validated";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
27
39
|
const resource = agentUrl;
|
|
28
40
|
if (!tokenEndpoint) {
|
|
29
41
|
steps.push({
|
|
@@ -35,6 +47,8 @@ async function runAuthDiagnosis(agent, options = {}) {
|
|
|
35
47
|
name: "token_refresh_attempt",
|
|
36
48
|
error: "No saved oauth_client.client_id \u2014 cannot attempt refresh without it"
|
|
37
49
|
});
|
|
50
|
+
} else if (issuerError) {
|
|
51
|
+
steps.push({ name: "token_refresh_attempt", error: issuerError });
|
|
38
52
|
} else {
|
|
39
53
|
const refreshCapture = await attemptTokenRefresh({
|
|
40
54
|
tokenEndpoint,
|
|
@@ -28,8 +28,10 @@ export interface FileOAuthStorageOptions {
|
|
|
28
28
|
*
|
|
29
29
|
* Does NOT touch fields outside the OAuth envelope (`auth_token`, custom
|
|
30
30
|
* fields) when saving — the MCP provider only mutates `oauth_tokens`,
|
|
31
|
-
* `oauth_client`, `oauth_resource`,
|
|
32
|
-
*
|
|
31
|
+
* `oauth_client`, `oauth_resource`, `oauth_code_verifier`, and `oauth_discovery_state`.
|
|
32
|
+
* An own property explicitly set to undefined clears tokens, client, verifier
|
|
33
|
+
* or discovery state. Omitted properties preserve stored values for partial
|
|
34
|
+
* callers. Other fields keep their existing merge semantics.
|
|
33
35
|
*
|
|
34
36
|
* @example
|
|
35
37
|
* ```ts
|
|
@@ -28,8 +28,10 @@ export interface FileOAuthStorageOptions {
|
|
|
28
28
|
*
|
|
29
29
|
* Does NOT touch fields outside the OAuth envelope (`auth_token`, custom
|
|
30
30
|
* fields) when saving — the MCP provider only mutates `oauth_tokens`,
|
|
31
|
-
* `oauth_client`, `oauth_resource`,
|
|
32
|
-
*
|
|
31
|
+
* `oauth_client`, `oauth_resource`, `oauth_code_verifier`, and `oauth_discovery_state`.
|
|
32
|
+
* An own property explicitly set to undefined clears tokens, client, verifier
|
|
33
|
+
* or discovery state. Omitted properties preserve stored values for partial
|
|
34
|
+
* callers. Other fields keep their existing merge semantics.
|
|
33
35
|
*
|
|
34
36
|
* @example
|
|
35
37
|
* ```ts
|
|
@@ -75,7 +75,8 @@ function createFileOAuthStorage(options) {
|
|
|
75
75
|
oauth_client: stored.oauth_client,
|
|
76
76
|
oauth_resource: stored.oauth_resource,
|
|
77
77
|
oauth_client_credentials: stored.oauth_client_credentials,
|
|
78
|
-
oauth_code_verifier: stored.oauth_code_verifier
|
|
78
|
+
oauth_code_verifier: stored.oauth_code_verifier,
|
|
79
|
+
oauth_discovery_state: stored.oauth_discovery_state
|
|
79
80
|
};
|
|
80
81
|
},
|
|
81
82
|
async saveAgent(agent) {
|
|
@@ -93,6 +94,18 @@ function createFileOAuthStorage(options) {
|
|
|
93
94
|
...agent.oauth_client_credentials !== void 0 ? { oauth_client_credentials: agent.oauth_client_credentials } : {},
|
|
94
95
|
...agent.oauth_code_verifier !== void 0 ? { oauth_code_verifier: agent.oauth_code_verifier } : {}
|
|
95
96
|
};
|
|
97
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_tokens") && agent.oauth_tokens === void 0) {
|
|
98
|
+
delete next.oauth_tokens;
|
|
99
|
+
}
|
|
100
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_client") && agent.oauth_client === void 0) {
|
|
101
|
+
delete next.oauth_client;
|
|
102
|
+
}
|
|
103
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_code_verifier") && agent.oauth_code_verifier === void 0) {
|
|
104
|
+
delete next.oauth_code_verifier;
|
|
105
|
+
}
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_discovery_state") && agent.oauth_discovery_state === void 0) {
|
|
107
|
+
delete next.oauth_discovery_state;
|
|
108
|
+
} else if (agent.oauth_discovery_state !== void 0) next.oauth_discovery_state = agent.oauth_discovery_state;
|
|
96
109
|
if (agent.oauth_resource === void 0) delete next.oauth_resource;
|
|
97
110
|
config.agents[key] = next;
|
|
98
111
|
await writeConfig(config);
|
|
@@ -42,7 +42,8 @@ function createFileOAuthStorage(options) {
|
|
|
42
42
|
oauth_client: stored.oauth_client,
|
|
43
43
|
oauth_resource: stored.oauth_resource,
|
|
44
44
|
oauth_client_credentials: stored.oauth_client_credentials,
|
|
45
|
-
oauth_code_verifier: stored.oauth_code_verifier
|
|
45
|
+
oauth_code_verifier: stored.oauth_code_verifier,
|
|
46
|
+
oauth_discovery_state: stored.oauth_discovery_state
|
|
46
47
|
};
|
|
47
48
|
},
|
|
48
49
|
async saveAgent(agent) {
|
|
@@ -60,6 +61,18 @@ function createFileOAuthStorage(options) {
|
|
|
60
61
|
...agent.oauth_client_credentials !== void 0 ? { oauth_client_credentials: agent.oauth_client_credentials } : {},
|
|
61
62
|
...agent.oauth_code_verifier !== void 0 ? { oauth_code_verifier: agent.oauth_code_verifier } : {}
|
|
62
63
|
};
|
|
64
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_tokens") && agent.oauth_tokens === void 0) {
|
|
65
|
+
delete next.oauth_tokens;
|
|
66
|
+
}
|
|
67
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_client") && agent.oauth_client === void 0) {
|
|
68
|
+
delete next.oauth_client;
|
|
69
|
+
}
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_code_verifier") && agent.oauth_code_verifier === void 0) {
|
|
71
|
+
delete next.oauth_code_verifier;
|
|
72
|
+
}
|
|
73
|
+
if (Object.prototype.hasOwnProperty.call(agent, "oauth_discovery_state") && agent.oauth_discovery_state === void 0) {
|
|
74
|
+
delete next.oauth_discovery_state;
|
|
75
|
+
} else if (agent.oauth_discovery_state !== void 0) next.oauth_discovery_state = agent.oauth_discovery_state;
|
|
63
76
|
if (agent.oauth_resource === void 0) delete next.oauth_resource;
|
|
64
77
|
config.agents[key] = next;
|
|
65
78
|
await writeConfig(config);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```typescript
|
|
8
|
-
* import { MCPOAuthProvider, CLIFlowHandler } from '@adcp/sdk';
|
|
8
|
+
* import { MCPOAuthProvider, CLIFlowHandler, DEFAULT_CLIENT_METADATA } from '@adcp/sdk/auth';
|
|
9
|
+
* import type { AgentConfig } from '@adcp/sdk';
|
|
9
10
|
*
|
|
10
11
|
* // Agent config - tokens will be stored here
|
|
11
12
|
* const agent: AgentConfig = {
|
|
@@ -13,14 +14,15 @@
|
|
|
13
14
|
* name: 'My Agent',
|
|
14
15
|
* agent_uri: 'https://agent.example.com/mcp',
|
|
15
16
|
* protocol: 'mcp',
|
|
16
|
-
* //
|
|
17
|
-
* // oauth_tokens: { access_token: '...', refresh_token: '...' }
|
|
17
|
+
* // A fresh interactive flow saves issuer-bound tokens here.
|
|
18
18
|
* };
|
|
19
19
|
*
|
|
20
20
|
* // Create provider with CLI flow handler
|
|
21
21
|
* const provider = new MCPOAuthProvider({
|
|
22
|
+
* allowInteractiveAuthorization: true,
|
|
22
23
|
* agent,
|
|
23
24
|
* flowHandler: new CLIFlowHandler(),
|
|
25
|
+
* clientMetadata: DEFAULT_CLIENT_METADATA,
|
|
24
26
|
* storage: myConfigStorage // Optional: persists tokens to file/db
|
|
25
27
|
* });
|
|
26
28
|
*
|
|
@@ -99,9 +101,10 @@ export declare function createCLIOAuthProvider(agent: AgentConfig, options?: {
|
|
|
99
101
|
* runs, scheduled jobs, and other non-interactive contexts where you've
|
|
100
102
|
* already saved tokens via `createCLIOAuthProvider`.
|
|
101
103
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
104
|
+
* A failed grant can throw `OAuthError` with `owner_reauthorization_required`;
|
|
105
|
+
* missing or mismatched issuer bindings throw `oauth_issuer_required` or `oauth_issuer_mismatch`.
|
|
106
|
+
* The owner can run `adcp <alias> --clear-oauth`, then
|
|
107
|
+
* `adcp <alias> --oauth` to start a fresh browser sign-in.
|
|
105
108
|
*/
|
|
106
109
|
export declare function createNonInteractiveOAuthProvider(agent: AgentConfig, options?: {
|
|
107
110
|
/** Hint shown in error messages if an interactive flow is attempted. */
|
|
@@ -143,5 +146,6 @@ export { createFileOAuthStorage, type FileOAuthStorageOptions } from './file-sto
|
|
|
143
146
|
export { exchangeClientCredentials, ensureClientCredentialsTokens, ClientCredentialsExchangeError, type ExchangeClientCredentialsOptions, type EnsureClientCredentialsOptions, } from './ClientCredentialsFlow.mjs';
|
|
144
147
|
export { resolveSecret, isEnvSecretReference, toEnvSecretReference, extractEnvSecretName, MissingEnvSecretError, } from './secret-resolver.mjs';
|
|
145
148
|
export type { AgentOAuthClientCredentials } from '../../types/adcp.mjs';
|
|
149
|
+
export { AgentOAuthClientCredentialsSchema } from './client-credentials-schema.mjs';
|
|
146
150
|
export { bindAgentStorage, getAgentStorage, unbindAgentStorage } from './storage-registry.mjs';
|
|
147
151
|
export { InvalidTokenError, InsufficientScopeError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```typescript
|
|
8
|
-
* import { MCPOAuthProvider, CLIFlowHandler } from '@adcp/sdk';
|
|
8
|
+
* import { MCPOAuthProvider, CLIFlowHandler, DEFAULT_CLIENT_METADATA } from '@adcp/sdk/auth';
|
|
9
|
+
* import type { AgentConfig } from '@adcp/sdk';
|
|
9
10
|
*
|
|
10
11
|
* // Agent config - tokens will be stored here
|
|
11
12
|
* const agent: AgentConfig = {
|
|
@@ -13,14 +14,15 @@
|
|
|
13
14
|
* name: 'My Agent',
|
|
14
15
|
* agent_uri: 'https://agent.example.com/mcp',
|
|
15
16
|
* protocol: 'mcp',
|
|
16
|
-
* //
|
|
17
|
-
* // oauth_tokens: { access_token: '...', refresh_token: '...' }
|
|
17
|
+
* // A fresh interactive flow saves issuer-bound tokens here.
|
|
18
18
|
* };
|
|
19
19
|
*
|
|
20
20
|
* // Create provider with CLI flow handler
|
|
21
21
|
* const provider = new MCPOAuthProvider({
|
|
22
|
+
* allowInteractiveAuthorization: true,
|
|
22
23
|
* agent,
|
|
23
24
|
* flowHandler: new CLIFlowHandler(),
|
|
25
|
+
* clientMetadata: DEFAULT_CLIENT_METADATA,
|
|
24
26
|
* storage: myConfigStorage // Optional: persists tokens to file/db
|
|
25
27
|
* });
|
|
26
28
|
*
|
|
@@ -99,9 +101,10 @@ export declare function createCLIOAuthProvider(agent: AgentConfig, options?: {
|
|
|
99
101
|
* runs, scheduled jobs, and other non-interactive contexts where you've
|
|
100
102
|
* already saved tokens via `createCLIOAuthProvider`.
|
|
101
103
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
104
|
+
* A failed grant can throw `OAuthError` with `owner_reauthorization_required`;
|
|
105
|
+
* missing or mismatched issuer bindings throw `oauth_issuer_required` or `oauth_issuer_mismatch`.
|
|
106
|
+
* The owner can run `adcp <alias> --clear-oauth`, then
|
|
107
|
+
* `adcp <alias> --oauth` to start a fresh browser sign-in.
|
|
105
108
|
*/
|
|
106
109
|
export declare function createNonInteractiveOAuthProvider(agent: AgentConfig, options?: {
|
|
107
110
|
/** Hint shown in error messages if an interactive flow is attempted. */
|
|
@@ -143,5 +146,6 @@ export { createFileOAuthStorage, type FileOAuthStorageOptions } from './file-sto
|
|
|
143
146
|
export { exchangeClientCredentials, ensureClientCredentialsTokens, ClientCredentialsExchangeError, type ExchangeClientCredentialsOptions, type EnsureClientCredentialsOptions, } from './ClientCredentialsFlow';
|
|
144
147
|
export { resolveSecret, isEnvSecretReference, toEnvSecretReference, extractEnvSecretName, MissingEnvSecretError, } from './secret-resolver';
|
|
145
148
|
export type { AgentOAuthClientCredentials } from '../../types/adcp';
|
|
149
|
+
export { AgentOAuthClientCredentialsSchema } from './client-credentials-schema';
|
|
146
150
|
export { bindAgentStorage, getAgentStorage, unbindAgentStorage } from './storage-registry';
|
|
147
151
|
export { InvalidTokenError, InsufficientScopeError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|