@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.
- package/bin/adcp.js +6 -1
- 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/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/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/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 +7 -7
- package/docs/migration-oauth-issuer-bindings.md +14 -79
- 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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
* //
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
* //
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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.
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
176
|
-
if (this.agent.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
273
|
+
this.agent.oauth_tokens = void 0;
|
|
221
274
|
break;
|
|
222
275
|
case "client":
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
281
|
+
this.pendingDiscoveryState = void 0;
|
|
282
|
+
this.agent.oauth_discovery_state = void 0;
|
|
228
283
|
break;
|
|
229
284
|
case "verifier":
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
296
|
+
* Persist a credential update without modifying independent browser state.
|
|
241
297
|
*/
|
|
242
|
-
async
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
165
|
-
if (this.agent.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
263
|
+
this.agent.oauth_tokens = void 0;
|
|
210
264
|
break;
|
|
211
265
|
case "client":
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
271
|
+
this.pendingDiscoveryState = void 0;
|
|
272
|
+
this.agent.oauth_discovery_state = void 0;
|
|
217
273
|
break;
|
|
218
274
|
case "verifier":
|
|
219
|
-
|
|
220
|
-
|
|
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
|
|
286
|
+
* Persist a credential update without modifying independent browser state.
|
|
230
287
|
*/
|
|
231
|
-
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) {
|
|
232
298
|
if (this.storage) {
|
|
233
|
-
await this.storage.saveAgent(
|
|
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.
|
|
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
|
|
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
|
}
|