@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
package/bin/adcp.js CHANGED
@@ -6659,7 +6659,12 @@ credential material — never sync or commit.
6659
6659
  }
6660
6660
 
6661
6661
  const agentConfig = getAgent(alias);
6662
- if (!agentConfig.oauth_tokens) {
6662
+ if (
6663
+ !agentConfig.oauth_tokens &&
6664
+ !agentConfig.oauth_client &&
6665
+ !agentConfig.oauth_code_verifier &&
6666
+ !agentConfig.oauth_discovery_state
6667
+ ) {
6663
6668
  console.log(`\nAgent '${alias}' has no OAuth tokens to clear.\n`);
6664
6669
  process.exit(0);
6665
6670
  }
@@ -247,7 +247,10 @@ async function ensureClientCredentialsTokens(agent, options = {}) {
247
247
  });
248
248
  agent.oauth_tokens = tokens;
249
249
  if (options.storage) {
250
- await options.storage.saveAgent(agent);
250
+ const refreshedAgent = { ...agent };
251
+ delete refreshedAgent.oauth_code_verifier;
252
+ delete refreshedAgent.oauth_discovery_state;
253
+ await options.storage.saveAgent(refreshedAgent);
251
254
  }
252
255
  return tokens;
253
256
  } finally {
@@ -222,7 +222,10 @@ async function ensureClientCredentialsTokens(agent, options = {}) {
222
222
  });
223
223
  agent.oauth_tokens = tokens;
224
224
  if (options.storage) {
225
- await options.storage.saveAgent(agent);
225
+ const refreshedAgent = { ...agent };
226
+ delete refreshedAgent.oauth_code_verifier;
227
+ delete refreshedAgent.oauth_discovery_state;
228
+ await options.storage.saveAgent(refreshedAgent);
226
229
  }
227
230
  return tokens;
228
231
  } finally {
@@ -20,13 +20,15 @@ export declare function assertLegacyOAuthSdk(): void;
20
20
  * name: 'My Agent',
21
21
  * agent_uri: 'https://agent.example.com/mcp',
22
22
  * protocol: 'mcp',
23
- * // OAuth tokens stored here after auth flow
24
- * oauth_tokens: { access_token: '...', refresh_token: '...' }
23
+ * // A fresh interactive flow saves issuer-bound credentials afterward.
25
24
  * };
26
25
  *
26
+ * const flowHandler = new CLIFlowHandler();
27
27
  * const provider = new MCPOAuthProvider({
28
28
  * agent,
29
- * flowHandler: new CLIFlowHandler(),
29
+ * flowHandler,
30
+ * clientMetadata: { ...DEFAULT_CLIENT_METADATA, redirect_uris: [flowHandler.getRedirectUrl().toString()] },
31
+ * allowInteractiveAuthorization: true,
30
32
  * storage: myConfigStorage // Optional: persists tokens
31
33
  * });
32
34
  *
@@ -42,6 +44,8 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
42
44
  private readonly _clientMetadata;
43
45
  private readonly allowHttp;
44
46
  private readonly configuredResourceOverride?;
47
+ private readonly allowInteractiveAuthorization;
48
+ private pendingDiscoveryState?;
45
49
  constructor(config: OAuthProviderConfig);
46
50
  /**
47
51
  * Create a provider for CLI usage
@@ -73,7 +77,9 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
73
77
  /**
74
78
  * Load client information from agent config
75
79
  */
76
- clientInformation(): Promise<OAuthClientInformation | undefined>;
80
+ clientInformation(ctx?: {
81
+ issuer: string;
82
+ }): Promise<OAuthClientInformation | undefined>;
77
83
  /**
78
84
  * Save client information after dynamic registration
79
85
  */
@@ -81,7 +87,9 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
81
87
  /**
82
88
  * Load existing tokens from agent config
83
89
  */
84
- tokens(): Promise<OAuthTokens | undefined>;
90
+ tokens(ctx?: {
91
+ issuer: string;
92
+ }): Promise<OAuthTokens | undefined>;
85
93
  /**
86
94
  * Save tokens after authorization
87
95
  * Also cleans up the temporary code verifier
@@ -94,6 +102,9 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
94
102
  /** Persist the authorization-server identity across the callback leg. */
95
103
  saveDiscoveryState(state: OAuthDiscoveryState): Promise<void>;
96
104
  discoveryState(): Promise<OAuthDiscoveryState | undefined>;
105
+ private validateDiscoveryState;
106
+ private checkIssuerContext;
107
+ private ownerReauthorizationRequired;
97
108
  /**
98
109
  * Save PKCE code verifier
99
110
  */
@@ -108,9 +119,11 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
108
119
  * Invalidate credentials when server indicates they're invalid
109
120
  */
110
121
  invalidateCredentials(scope: Parameters<NonNullable<OAuthClientProvider['invalidateCredentials']>>[0]): Promise<void>;
122
+ private clearCredentials;
111
123
  /**
112
- * Persist agent config to storage if configured
124
+ * Persist a credential update without modifying independent browser state.
113
125
  */
126
+ private persistCredentials;
114
127
  private persistAgent;
115
128
  /**
116
129
  * Wait for the OAuth callback
@@ -20,13 +20,15 @@ export declare function assertLegacyOAuthSdk(): void;
20
20
  * name: 'My Agent',
21
21
  * agent_uri: 'https://agent.example.com/mcp',
22
22
  * protocol: 'mcp',
23
- * // OAuth tokens stored here after auth flow
24
- * oauth_tokens: { access_token: '...', refresh_token: '...' }
23
+ * // A fresh interactive flow saves issuer-bound credentials afterward.
25
24
  * };
26
25
  *
26
+ * const flowHandler = new CLIFlowHandler();
27
27
  * const provider = new MCPOAuthProvider({
28
28
  * agent,
29
- * flowHandler: new CLIFlowHandler(),
29
+ * flowHandler,
30
+ * clientMetadata: { ...DEFAULT_CLIENT_METADATA, redirect_uris: [flowHandler.getRedirectUrl().toString()] },
31
+ * allowInteractiveAuthorization: true,
30
32
  * storage: myConfigStorage // Optional: persists tokens
31
33
  * });
32
34
  *
@@ -42,6 +44,8 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
42
44
  private readonly _clientMetadata;
43
45
  private readonly allowHttp;
44
46
  private readonly configuredResourceOverride?;
47
+ private readonly allowInteractiveAuthorization;
48
+ private pendingDiscoveryState?;
45
49
  constructor(config: OAuthProviderConfig);
46
50
  /**
47
51
  * Create a provider for CLI usage
@@ -73,7 +77,9 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
73
77
  /**
74
78
  * Load client information from agent config
75
79
  */
76
- clientInformation(): Promise<OAuthClientInformation | undefined>;
80
+ clientInformation(ctx?: {
81
+ issuer: string;
82
+ }): Promise<OAuthClientInformation | undefined>;
77
83
  /**
78
84
  * Save client information after dynamic registration
79
85
  */
@@ -81,7 +87,9 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
81
87
  /**
82
88
  * Load existing tokens from agent config
83
89
  */
84
- tokens(): Promise<OAuthTokens | undefined>;
90
+ tokens(ctx?: {
91
+ issuer: string;
92
+ }): Promise<OAuthTokens | undefined>;
85
93
  /**
86
94
  * Save tokens after authorization
87
95
  * Also cleans up the temporary code verifier
@@ -94,6 +102,9 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
94
102
  /** Persist the authorization-server identity across the callback leg. */
95
103
  saveDiscoveryState(state: OAuthDiscoveryState): Promise<void>;
96
104
  discoveryState(): Promise<OAuthDiscoveryState | undefined>;
105
+ private validateDiscoveryState;
106
+ private checkIssuerContext;
107
+ private ownerReauthorizationRequired;
97
108
  /**
98
109
  * Save PKCE code verifier
99
110
  */
@@ -108,9 +119,11 @@ export declare class MCPOAuthProvider implements OAuthClientProvider {
108
119
  * Invalidate credentials when server indicates they're invalid
109
120
  */
110
121
  invalidateCredentials(scope: Parameters<NonNullable<OAuthClientProvider['invalidateCredentials']>>[0]): Promise<void>;
122
+ private clearCredentials;
111
123
  /**
112
- * Persist agent config to storage if configured
124
+ * Persist a credential update without modifying independent browser state.
113
125
  */
126
+ private persistCredentials;
114
127
  private persistAgent;
115
128
  /**
116
129
  * Wait for the OAuth callback
@@ -59,6 +59,8 @@ class MCPOAuthProvider {
59
59
  _clientMetadata;
60
60
  allowHttp;
61
61
  configuredResourceOverride;
62
+ allowInteractiveAuthorization;
63
+ pendingDiscoveryState;
62
64
  constructor(config) {
63
65
  this.agent = config.agent;
64
66
  this.storage = config.storage;
@@ -66,6 +68,7 @@ class MCPOAuthProvider {
66
68
  this._clientMetadata = config.clientMetadata;
67
69
  this.allowHttp = config.allowHttp === true;
68
70
  this.configuredResourceOverride = config.resourceOverride;
71
+ this.allowInteractiveAuthorization = config.allowInteractiveAuthorization === true;
69
72
  }
70
73
  /**
71
74
  * Create a provider for CLI usage
@@ -82,7 +85,8 @@ class MCPOAuthProvider {
82
85
  storage,
83
86
  clientMetadata,
84
87
  allowHttp: options?.allowHttp,
85
- resourceOverride: options?.resourceOverride
88
+ resourceOverride: options?.resourceOverride,
89
+ allowInteractiveAuthorization: true
86
90
  });
87
91
  }
88
92
  // ========================================
@@ -123,7 +127,11 @@ class MCPOAuthProvider {
123
127
  /**
124
128
  * Load client information from agent config
125
129
  */
126
- async clientInformation() {
130
+ async clientInformation(ctx) {
131
+ const issuer = this.checkIssuerContext(ctx);
132
+ if (issuer && !this.agent.oauth_client && !this.allowInteractiveAuthorization) {
133
+ throw this.ownerReauthorizationRequired();
134
+ }
127
135
  if (this.agent.oauth_client) {
128
136
  if (this.agent.oauth_client.client_secret) assertLegacyOAuthSdk();
129
137
  (0, import_types.assertOAuthCredentialIssuer)(this.agent.oauth_client, void 0, !!this.agent.oauth_client.client_secret);
@@ -136,12 +144,13 @@ class MCPOAuthProvider {
136
144
  */
137
145
  async saveClientInformation(clientInfo) {
138
146
  this.agent.oauth_client = (0, import_types.fromMCPClientInfo)(clientInfo);
139
- await this.persistAgent();
147
+ await this.persistCredentials();
140
148
  }
141
149
  /**
142
150
  * Load existing tokens from agent config
143
151
  */
144
- async tokens() {
152
+ async tokens(ctx) {
153
+ this.checkIssuerContext(ctx);
145
154
  if (this.agent.oauth_tokens) {
146
155
  if (this.agent.oauth_tokens.refresh_token) assertLegacyOAuthSdk();
147
156
  (0, import_types.assertOAuthCredentialIssuer)(this.agent.oauth_tokens, void 0, !!this.agent.oauth_tokens.refresh_token);
@@ -155,14 +164,18 @@ class MCPOAuthProvider {
155
164
  */
156
165
  async saveTokens(tokens) {
157
166
  this.agent.oauth_tokens = (0, import_types.fromMCPTokens)(tokens);
158
- delete this.agent.oauth_code_verifier;
159
- delete this.agent.oauth_discovery_state;
160
- await this.persistAgent();
167
+ if (this.allowInteractiveAuthorization) {
168
+ this.agent.oauth_code_verifier = void 0;
169
+ this.agent.oauth_discovery_state = void 0;
170
+ }
171
+ this.pendingDiscoveryState = void 0;
172
+ await this.persistCredentials();
161
173
  }
162
174
  /**
163
175
  * Redirect user to authorization URL
164
176
  */
165
177
  async redirectToAuthorization(authorizationUrl) {
178
+ if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired("interactive_required");
166
179
  try {
167
180
  await this.flowHandler.redirectToAuthorization(authorizationUrl);
168
181
  } catch (error) {
@@ -172,23 +185,51 @@ class MCPOAuthProvider {
172
185
  }
173
186
  /** Persist the authorization-server identity across the callback leg. */
174
187
  async saveDiscoveryState(state) {
175
- const issuer = String(state.authorizationServerUrl);
176
- if (this.agent.oauth_tokens?.refresh_token) {
177
- (0, import_types.assertOAuthCredentialIssuer)(this.agent.oauth_tokens, issuer, true);
178
- }
179
- if (this.agent.oauth_client?.client_secret) {
180
- (0, import_types.assertOAuthCredentialIssuer)(this.agent.oauth_client, issuer, true);
188
+ this.validateDiscoveryState(state);
189
+ if (!this.agent.oauth_client && !this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
190
+ this.pendingDiscoveryState = structuredClone(state);
191
+ if (this.allowInteractiveAuthorization) {
192
+ this.agent.oauth_discovery_state = structuredClone(state);
193
+ await this.persistAgent();
181
194
  }
182
- this.agent.oauth_discovery_state = state;
183
- await this.persistAgent();
184
195
  }
185
196
  async discoveryState() {
186
- return this.agent.oauth_code_verifier ? this.agent.oauth_discovery_state : void 0;
197
+ if (!this.allowInteractiveAuthorization) return void 0;
198
+ if (!this.agent.oauth_code_verifier) return void 0;
199
+ const state = this.pendingDiscoveryState ?? this.agent.oauth_discovery_state;
200
+ if (!state) return void 0;
201
+ this.validateDiscoveryState(state);
202
+ this.pendingDiscoveryState = structuredClone(state);
203
+ return structuredClone(state);
204
+ }
205
+ validateDiscoveryState(state) {
206
+ (0, import_types.assertOAuthServerIssuer)(state.authorizationServerMetadata, String(state.authorizationServerUrl));
207
+ this.checkIssuerContext({ issuer: String(state.authorizationServerUrl) });
208
+ }
209
+ checkIssuerContext(ctx) {
210
+ const issuer = ctx?.issuer ?? this.pendingDiscoveryState?.authorizationServerUrl;
211
+ if (issuer !== void 0) {
212
+ if (this.agent.oauth_tokens) {
213
+ (0, import_types.assertOAuthCredentialIssuer)(this.agent.oauth_tokens, issuer, !!this.agent.oauth_tokens.refresh_token);
214
+ }
215
+ if (this.agent.oauth_client) {
216
+ (0, import_types.assertOAuthCredentialIssuer)(this.agent.oauth_client, issuer, !!this.agent.oauth_client.client_secret);
217
+ }
218
+ }
219
+ return issuer;
220
+ }
221
+ ownerReauthorizationRequired(code = "owner_reauthorization_required") {
222
+ return new import_types.OAuthError(
223
+ "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.",
224
+ code,
225
+ this.agent.id
226
+ );
187
227
  }
188
228
  /**
189
229
  * Save PKCE code verifier
190
230
  */
191
231
  async saveCodeVerifier(codeVerifier) {
232
+ if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
192
233
  this.agent.oauth_code_verifier = codeVerifier;
193
234
  await this.persistAgent();
194
235
  }
@@ -198,6 +239,7 @@ class MCPOAuthProvider {
198
239
  * retrieves it here during token exchange.
199
240
  */
200
241
  async codeVerifier() {
242
+ if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
201
243
  if (!this.agent.oauth_code_verifier) {
202
244
  throw new Error(
203
245
  "No PKCE code verifier found. The OAuth flow may have been interrupted or the agent config was modified. Please try authenticating again."
@@ -209,26 +251,40 @@ class MCPOAuthProvider {
209
251
  * Invalidate credentials when server indicates they're invalid
210
252
  */
211
253
  async invalidateCredentials(scope) {
254
+ if (!this.allowInteractiveAuthorization && (scope === "discovery" || scope === "verifier")) {
255
+ this.pendingDiscoveryState = void 0;
256
+ return;
257
+ }
258
+ if (!this.allowInteractiveAuthorization && (scope === "all" || scope === "client" || scope === "tokens")) {
259
+ throw this.ownerReauthorizationRequired();
260
+ }
261
+ await this.clearCredentials(scope);
262
+ }
263
+ async clearCredentials(scope) {
212
264
  switch (scope) {
213
265
  case "all":
214
- delete this.agent.oauth_discovery_state;
215
- delete this.agent.oauth_tokens;
216
- delete this.agent.oauth_client;
217
- delete this.agent.oauth_code_verifier;
266
+ this.pendingDiscoveryState = void 0;
267
+ this.agent.oauth_discovery_state = void 0;
268
+ this.agent.oauth_tokens = void 0;
269
+ this.agent.oauth_client = void 0;
270
+ this.agent.oauth_code_verifier = void 0;
218
271
  break;
219
272
  case "tokens":
220
- delete this.agent.oauth_tokens;
273
+ this.agent.oauth_tokens = void 0;
221
274
  break;
222
275
  case "client":
223
- delete this.agent.oauth_discovery_state;
224
- delete this.agent.oauth_client;
276
+ this.pendingDiscoveryState = void 0;
277
+ this.agent.oauth_discovery_state = void 0;
278
+ this.agent.oauth_client = void 0;
225
279
  break;
226
280
  case "discovery":
227
- delete this.agent.oauth_discovery_state;
281
+ this.pendingDiscoveryState = void 0;
282
+ this.agent.oauth_discovery_state = void 0;
228
283
  break;
229
284
  case "verifier":
230
- delete this.agent.oauth_discovery_state;
231
- delete this.agent.oauth_code_verifier;
285
+ this.pendingDiscoveryState = void 0;
286
+ this.agent.oauth_discovery_state = void 0;
287
+ this.agent.oauth_code_verifier = void 0;
232
288
  break;
233
289
  }
234
290
  await this.persistAgent();
@@ -237,11 +293,20 @@ class MCPOAuthProvider {
237
293
  // Additional methods
238
294
  // ========================================
239
295
  /**
240
- * Persist agent config to storage if configured
296
+ * Persist a credential update without modifying independent browser state.
241
297
  */
242
- async persistAgent() {
298
+ async persistCredentials() {
299
+ let savedAgent = this.agent;
300
+ if (!this.allowInteractiveAuthorization) {
301
+ savedAgent = { ...this.agent };
302
+ delete savedAgent.oauth_code_verifier;
303
+ delete savedAgent.oauth_discovery_state;
304
+ }
305
+ await this.persistAgent(savedAgent);
306
+ }
307
+ async persistAgent(agent = this.agent) {
243
308
  if (this.storage) {
244
- await this.storage.saveAgent(this.agent);
309
+ await this.storage.saveAgent(agent);
245
310
  }
246
311
  }
247
312
  /**
@@ -283,7 +348,7 @@ class MCPOAuthProvider {
283
348
  * Clear all OAuth data for this agent
284
349
  */
285
350
  async clearAuth() {
286
- await this.invalidateCredentials("all");
351
+ await this.clearCredentials("all");
287
352
  }
288
353
  /**
289
354
  * Get the agent config this provider manages
@@ -10,6 +10,7 @@ import {
10
10
  toMCPClientInfo,
11
11
  fromMCPClientInfo,
12
12
  assertOAuthCredentialIssuer,
13
+ assertOAuthServerIssuer,
13
14
  OAuthError
14
15
  } from "./types.mjs";
15
16
  import { randomBytes } from "crypto";
@@ -48,6 +49,8 @@ class MCPOAuthProvider {
48
49
  _clientMetadata;
49
50
  allowHttp;
50
51
  configuredResourceOverride;
52
+ allowInteractiveAuthorization;
53
+ pendingDiscoveryState;
51
54
  constructor(config) {
52
55
  this.agent = config.agent;
53
56
  this.storage = config.storage;
@@ -55,6 +58,7 @@ class MCPOAuthProvider {
55
58
  this._clientMetadata = config.clientMetadata;
56
59
  this.allowHttp = config.allowHttp === true;
57
60
  this.configuredResourceOverride = config.resourceOverride;
61
+ this.allowInteractiveAuthorization = config.allowInteractiveAuthorization === true;
58
62
  }
59
63
  /**
60
64
  * Create a provider for CLI usage
@@ -71,7 +75,8 @@ class MCPOAuthProvider {
71
75
  storage,
72
76
  clientMetadata,
73
77
  allowHttp: options?.allowHttp,
74
- resourceOverride: options?.resourceOverride
78
+ resourceOverride: options?.resourceOverride,
79
+ allowInteractiveAuthorization: true
75
80
  });
76
81
  }
77
82
  // ========================================
@@ -112,7 +117,11 @@ class MCPOAuthProvider {
112
117
  /**
113
118
  * Load client information from agent config
114
119
  */
115
- 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
+ }
116
125
  if (this.agent.oauth_client) {
117
126
  if (this.agent.oauth_client.client_secret) assertLegacyOAuthSdk();
118
127
  assertOAuthCredentialIssuer(this.agent.oauth_client, void 0, !!this.agent.oauth_client.client_secret);
@@ -125,12 +134,13 @@ class MCPOAuthProvider {
125
134
  */
126
135
  async saveClientInformation(clientInfo) {
127
136
  this.agent.oauth_client = fromMCPClientInfo(clientInfo);
128
- await this.persistAgent();
137
+ await this.persistCredentials();
129
138
  }
130
139
  /**
131
140
  * Load existing tokens from agent config
132
141
  */
133
- async tokens() {
142
+ async tokens(ctx) {
143
+ this.checkIssuerContext(ctx);
134
144
  if (this.agent.oauth_tokens) {
135
145
  if (this.agent.oauth_tokens.refresh_token) assertLegacyOAuthSdk();
136
146
  assertOAuthCredentialIssuer(this.agent.oauth_tokens, void 0, !!this.agent.oauth_tokens.refresh_token);
@@ -144,14 +154,18 @@ class MCPOAuthProvider {
144
154
  */
145
155
  async saveTokens(tokens) {
146
156
  this.agent.oauth_tokens = fromMCPTokens(tokens);
147
- delete this.agent.oauth_code_verifier;
148
- delete this.agent.oauth_discovery_state;
149
- await this.persistAgent();
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();
150
163
  }
151
164
  /**
152
165
  * Redirect user to authorization URL
153
166
  */
154
167
  async redirectToAuthorization(authorizationUrl) {
168
+ if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired("interactive_required");
155
169
  try {
156
170
  await this.flowHandler.redirectToAuthorization(authorizationUrl);
157
171
  } catch (error) {
@@ -161,23 +175,51 @@ class MCPOAuthProvider {
161
175
  }
162
176
  /** Persist the authorization-server identity across the callback leg. */
163
177
  async saveDiscoveryState(state) {
164
- const issuer = String(state.authorizationServerUrl);
165
- if (this.agent.oauth_tokens?.refresh_token) {
166
- assertOAuthCredentialIssuer(this.agent.oauth_tokens, issuer, true);
167
- }
168
- if (this.agent.oauth_client?.client_secret) {
169
- assertOAuthCredentialIssuer(this.agent.oauth_client, issuer, true);
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();
170
184
  }
171
- this.agent.oauth_discovery_state = state;
172
- await this.persistAgent();
173
185
  }
174
186
  async discoveryState() {
175
- return this.agent.oauth_code_verifier ? this.agent.oauth_discovery_state : void 0;
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
+ );
176
217
  }
177
218
  /**
178
219
  * Save PKCE code verifier
179
220
  */
180
221
  async saveCodeVerifier(codeVerifier) {
222
+ if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
181
223
  this.agent.oauth_code_verifier = codeVerifier;
182
224
  await this.persistAgent();
183
225
  }
@@ -187,6 +229,7 @@ class MCPOAuthProvider {
187
229
  * retrieves it here during token exchange.
188
230
  */
189
231
  async codeVerifier() {
232
+ if (!this.allowInteractiveAuthorization) throw this.ownerReauthorizationRequired();
190
233
  if (!this.agent.oauth_code_verifier) {
191
234
  throw new Error(
192
235
  "No PKCE code verifier found. The OAuth flow may have been interrupted or the agent config was modified. Please try authenticating again."
@@ -198,26 +241,40 @@ class MCPOAuthProvider {
198
241
  * Invalidate credentials when server indicates they're invalid
199
242
  */
200
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) {
201
254
  switch (scope) {
202
255
  case "all":
203
- delete this.agent.oauth_discovery_state;
204
- delete this.agent.oauth_tokens;
205
- delete this.agent.oauth_client;
206
- delete this.agent.oauth_code_verifier;
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;
207
261
  break;
208
262
  case "tokens":
209
- delete this.agent.oauth_tokens;
263
+ this.agent.oauth_tokens = void 0;
210
264
  break;
211
265
  case "client":
212
- delete this.agent.oauth_discovery_state;
213
- delete this.agent.oauth_client;
266
+ this.pendingDiscoveryState = void 0;
267
+ this.agent.oauth_discovery_state = void 0;
268
+ this.agent.oauth_client = void 0;
214
269
  break;
215
270
  case "discovery":
216
- delete this.agent.oauth_discovery_state;
271
+ this.pendingDiscoveryState = void 0;
272
+ this.agent.oauth_discovery_state = void 0;
217
273
  break;
218
274
  case "verifier":
219
- delete this.agent.oauth_discovery_state;
220
- delete this.agent.oauth_code_verifier;
275
+ this.pendingDiscoveryState = void 0;
276
+ this.agent.oauth_discovery_state = void 0;
277
+ this.agent.oauth_code_verifier = void 0;
221
278
  break;
222
279
  }
223
280
  await this.persistAgent();
@@ -226,11 +283,20 @@ class MCPOAuthProvider {
226
283
  // Additional methods
227
284
  // ========================================
228
285
  /**
229
- * Persist agent config to storage if configured
286
+ * Persist a credential update without modifying independent browser state.
230
287
  */
231
- async persistAgent() {
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) {
232
298
  if (this.storage) {
233
- await this.storage.saveAgent(this.agent);
299
+ await this.storage.saveAgent(agent);
234
300
  }
235
301
  }
236
302
  /**
@@ -272,7 +338,7 @@ class MCPOAuthProvider {
272
338
  * Clear all OAuth data for this agent
273
339
  */
274
340
  async clearAuth() {
275
- await this.invalidateCredentials("all");
341
+ await this.clearCredentials("all");
276
342
  }
277
343
  /**
278
344
  * Get the agent config this provider manages
@@ -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 name to pass to `adcp --save-auth`.
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 name to pass to `adcp --save-auth`.
24
+ * attempted — e.g., the saved alias for `adcp <alias> --oauth`.
25
25
  */
26
26
  agentHint?: string;
27
27
  }