@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
@@ -33,7 +33,7 @@ class NonInteractiveFlowHandler {
33
33
  return this._redirectUrl;
34
34
  }
35
35
  async redirectToAuthorization() {
36
- const target = this.agentHint ? `adcp --save-auth ${this.agentHint} --oauth` : "adcp --save-auth <alias> <url> --oauth";
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 --save-auth ${this.agentHint} --oauth` : "adcp --save-auth <alias> <url> --oauth";
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
+ };
@@ -29,9 +29,9 @@ export interface FileOAuthStorageOptions {
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
31
  * `oauth_client`, `oauth_resource`, `oauth_code_verifier`, and `oauth_discovery_state`.
32
- * Those fields are authoritative: an absent value deletes the stored value.
33
- * Load the complete agent before constructing a provider; partial snapshots
34
- * cannot preserve omitted OAuth fields. Other fields keep merge semantics.
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.
35
35
  *
36
36
  * @example
37
37
  * ```ts
@@ -29,9 +29,9 @@ export interface FileOAuthStorageOptions {
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
31
  * `oauth_client`, `oauth_resource`, `oauth_code_verifier`, and `oauth_discovery_state`.
32
- * Those fields are authoritative: an absent value deletes the stored value.
33
- * Load the complete agent before constructing a provider; partial snapshots
34
- * cannot preserve omitted OAuth fields. Other fields keep merge semantics.
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.
35
35
  *
36
36
  * @example
37
37
  * ```ts
@@ -94,11 +94,18 @@ function createFileOAuthStorage(options) {
94
94
  ...agent.oauth_client_credentials !== void 0 ? { oauth_client_credentials: agent.oauth_client_credentials } : {},
95
95
  ...agent.oauth_code_verifier !== void 0 ? { oauth_code_verifier: agent.oauth_code_verifier } : {}
96
96
  };
97
- if (agent.oauth_tokens === void 0) delete next.oauth_tokens;
98
- if (agent.oauth_client === void 0) delete next.oauth_client;
99
- if (agent.oauth_code_verifier === void 0) delete next.oauth_code_verifier;
100
- if (agent.oauth_discovery_state === void 0) delete next.oauth_discovery_state;
101
- else next.oauth_discovery_state = agent.oauth_discovery_state;
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;
102
109
  if (agent.oauth_resource === void 0) delete next.oauth_resource;
103
110
  config.agents[key] = next;
104
111
  await writeConfig(config);
@@ -61,11 +61,18 @@ function createFileOAuthStorage(options) {
61
61
  ...agent.oauth_client_credentials !== void 0 ? { oauth_client_credentials: agent.oauth_client_credentials } : {},
62
62
  ...agent.oauth_code_verifier !== void 0 ? { oauth_code_verifier: agent.oauth_code_verifier } : {}
63
63
  };
64
- if (agent.oauth_tokens === void 0) delete next.oauth_tokens;
65
- if (agent.oauth_client === void 0) delete next.oauth_client;
66
- if (agent.oauth_code_verifier === void 0) delete next.oauth_code_verifier;
67
- if (agent.oauth_discovery_state === void 0) delete next.oauth_discovery_state;
68
- else next.oauth_discovery_state = agent.oauth_discovery_state;
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;
69
76
  if (agent.oauth_resource === void 0) delete next.oauth_resource;
70
77
  config.agents[key] = next;
71
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
- * // After OAuth flow completes:
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
- * If refresh fails (e.g., the refresh_token is revoked or expired), the
103
- * MCP SDK will throw `UnauthorizedError` at call time — the caller should
104
- * treat that as a signal to run `adcp --save-auth <alias> --oauth` and retry.
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
- * // After OAuth flow completes:
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
- * If refresh fails (e.g., the refresh_token is revoked or expired), the
103
- * MCP SDK will throw `UnauthorizedError` at call time — the caller should
104
- * treat that as a signal to run `adcp --save-auth <alias> --oauth` and retry.
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';
@@ -20,6 +20,7 @@ var oauth_exports = {};
20
20
  __export(oauth_exports, {
21
21
  AUTH_DIAGNOSIS_SCHEMA_VERSION: () => import_diagnose.AUTH_DIAGNOSIS_SCHEMA_VERSION,
22
22
  AgentChangedDuringFlowError: () => import_web_flow.AgentChangedDuringFlowError,
23
+ AgentOAuthClientCredentialsSchema: () => import_client_credentials_schema.AgentOAuthClientCredentialsSchema,
23
24
  AgentVanishedDuringFlowError: () => import_web_flow.AgentVanishedDuringFlowError,
24
25
  BrowserBindingRequiredError: () => import_web_flow.BrowserBindingRequiredError,
25
26
  CLIFlowHandler: () => import_CLIFlowHandler.CLIFlowHandler,
@@ -92,6 +93,7 @@ var import_authorization_required = require('./authorization-required.js');
92
93
  var import_file_storage = require('./file-storage.js');
93
94
  var import_ClientCredentialsFlow = require('./ClientCredentialsFlow.js');
94
95
  var import_secret_resolver = require('./secret-resolver.js');
96
+ var import_client_credentials_schema = require('./client-credentials-schema.js');
95
97
  var import_storage_registry = require('./storage-registry.js');
96
98
  var import_errors = require("@modelcontextprotocol/sdk/server/auth/errors.js");
97
99
  function createCLIOAuthProvider(agent, options) {
@@ -107,6 +109,7 @@ function createCLIOAuthProvider(agent, options) {
107
109
  ...options?.clientMetadata
108
110
  };
109
111
  return new import_MCPOAuthProvider2.MCPOAuthProvider({
112
+ allowInteractiveAuthorization: true,
110
113
  agent,
111
114
  flowHandler,
112
115
  storage: options?.storage,
@@ -123,6 +126,7 @@ function createNonInteractiveOAuthProvider(agent, options) {
123
126
  ...options?.clientMetadata
124
127
  };
125
128
  return new import_MCPOAuthProvider2.MCPOAuthProvider({
129
+ allowInteractiveAuthorization: false,
126
130
  agent,
127
131
  flowHandler,
128
132
  storage: options?.storage,
@@ -153,9 +157,10 @@ function hasValidOAuthTokens(agent) {
153
157
  return true;
154
158
  }
155
159
  function clearOAuthTokens(agent) {
156
- delete agent.oauth_tokens;
157
- delete agent.oauth_client;
158
- delete agent.oauth_code_verifier;
160
+ agent.oauth_tokens = void 0;
161
+ agent.oauth_client = void 0;
162
+ agent.oauth_code_verifier = void 0;
163
+ agent.oauth_discovery_state = void 0;
159
164
  }
160
165
  function getEffectiveAuthToken(agent) {
161
166
  if (hasValidOAuthTokens(agent)) {
@@ -167,6 +172,7 @@ function getEffectiveAuthToken(agent) {
167
172
  0 && (module.exports = {
168
173
  AUTH_DIAGNOSIS_SCHEMA_VERSION,
169
174
  AgentChangedDuringFlowError,
175
+ AgentOAuthClientCredentialsSchema,
170
176
  AgentVanishedDuringFlowError,
171
177
  BrowserBindingRequiredError,
172
178
  CLIFlowHandler,
@@ -43,6 +43,7 @@ function createCLIOAuthProvider(agent, options) {
43
43
  ...options?.clientMetadata
44
44
  };
45
45
  return new MCPOAuthProvider2({
46
+ allowInteractiveAuthorization: true,
46
47
  agent,
47
48
  flowHandler,
48
49
  storage: options?.storage,
@@ -59,6 +60,7 @@ function createNonInteractiveOAuthProvider(agent, options) {
59
60
  ...options?.clientMetadata
60
61
  };
61
62
  return new MCPOAuthProvider2({
63
+ allowInteractiveAuthorization: false,
62
64
  agent,
63
65
  flowHandler,
64
66
  storage: options?.storage,
@@ -89,9 +91,10 @@ function hasValidOAuthTokens(agent) {
89
91
  return true;
90
92
  }
91
93
  function clearOAuthTokens(agent) {
92
- delete agent.oauth_tokens;
93
- delete agent.oauth_client;
94
- delete agent.oauth_code_verifier;
94
+ agent.oauth_tokens = void 0;
95
+ agent.oauth_client = void 0;
96
+ agent.oauth_code_verifier = void 0;
97
+ agent.oauth_discovery_state = void 0;
95
98
  }
96
99
  function getEffectiveAuthToken(agent) {
97
100
  if (hasValidOAuthTokens(agent)) {
@@ -132,11 +135,13 @@ import {
132
135
  extractEnvSecretName,
133
136
  MissingEnvSecretError
134
137
  } from "./secret-resolver.mjs";
138
+ import { AgentOAuthClientCredentialsSchema } from "./client-credentials-schema.mjs";
135
139
  import { bindAgentStorage, getAgentStorage, unbindAgentStorage } from "./storage-registry.mjs";
136
140
  import { InvalidTokenError, InsufficientScopeError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
137
141
  export {
138
142
  AUTH_DIAGNOSIS_SCHEMA_VERSION,
139
143
  AgentChangedDuringFlowError,
144
+ AgentOAuthClientCredentialsSchema,
140
145
  AgentVanishedDuringFlowError,
141
146
  BrowserBindingRequiredError,
142
147
  CLIFlowHandler,
@@ -13,8 +13,10 @@ export type { AgentConfig, AgentOAuthTokens, AgentOAuthClient };
13
13
  * Agent config storage interface
14
14
  *
15
15
  * Implement this to persist OAuth tokens back to agent configuration.
16
- * Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim,
17
- * and remove OAuth fields absent from the authoritative agent snapshot.
16
+ * Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim.
17
+ * For oauth_tokens, oauth_client, oauth_code_verifier and oauth_discovery_state,
18
+ * an own undefined property clears; omission preserves the saved field.
19
+ * Resource overrides and other fields retain the storage backend's semantics.
18
20
  * This allows different storage backends (file, database, memory)
19
21
  * while keeping tokens in the agent config structure.
20
22
  */
@@ -76,6 +78,11 @@ export interface OAuthProviderConfig {
76
78
  storage?: OAuthConfigStorage;
77
79
  /** Authorization flow handler */
78
80
  flowHandler: OAuthFlowHandler;
81
+ /**
82
+ * Explicitly allow fresh registration, PKCE work, redirects and automatic
83
+ * credential invalidation. Defaults to false; CLI factories opt in.
84
+ */
85
+ allowInteractiveAuthorization?: boolean;
79
86
  /** OAuth client metadata (required - use DEFAULT_CLIENT_METADATA as base) */
80
87
  clientMetadata: OAuthClientMetadata;
81
88
  /**
@@ -13,8 +13,10 @@ export type { AgentConfig, AgentOAuthTokens, AgentOAuthClient };
13
13
  * Agent config storage interface
14
14
  *
15
15
  * Implement this to persist OAuth tokens back to agent configuration.
16
- * Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim,
17
- * and remove OAuth fields absent from the authoritative agent snapshot.
16
+ * Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim.
17
+ * For oauth_tokens, oauth_client, oauth_code_verifier and oauth_discovery_state,
18
+ * an own undefined property clears; omission preserves the saved field.
19
+ * Resource overrides and other fields retain the storage backend's semantics.
18
20
  * This allows different storage backends (file, database, memory)
19
21
  * while keeping tokens in the agent config structure.
20
22
  */
@@ -76,6 +78,11 @@ export interface OAuthProviderConfig {
76
78
  storage?: OAuthConfigStorage;
77
79
  /** Authorization flow handler */
78
80
  flowHandler: OAuthFlowHandler;
81
+ /**
82
+ * Explicitly allow fresh registration, PKCE work, redirects and automatic
83
+ * credential invalidation. Defaults to false; CLI factories opt in.
84
+ */
85
+ allowInteractiveAuthorization?: boolean;
79
86
  /** OAuth client metadata (required - use DEFAULT_CLIENT_METADATA as base) */
80
87
  clientMetadata: OAuthClientMetadata;
81
88
  /**
@@ -103,10 +103,11 @@ function fromMCPClientInfo(info) {
103
103
  };
104
104
  }
105
105
  function isOAuthIssuer(value) {
106
- if (typeof value !== "string" || value.length === 0) return false;
106
+ if (typeof value !== "string" || !value || /[\u0000-\u0020\u007f\\]/.test(value)) return false;
107
107
  try {
108
108
  const url = new URL(value);
109
- return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password && !url.search && !url.hash;
109
+ const rawAuthority = value.match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i)?.[1];
110
+ return (url.protocol === "https:" || url.protocol === "http:") && !rawAuthority?.includes("@") && !url.username && !url.password && !url.search && !url.hash;
110
111
  } catch {
111
112
  return false;
112
113
  }
@@ -71,10 +71,11 @@ function fromMCPClientInfo(info) {
71
71
  };
72
72
  }
73
73
  function isOAuthIssuer(value) {
74
- if (typeof value !== "string" || value.length === 0) return false;
74
+ if (typeof value !== "string" || !value || /[\u0000-\u0020\u007f\\]/.test(value)) return false;
75
75
  try {
76
76
  const url = new URL(value);
77
- return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password && !url.search && !url.hash;
77
+ const rawAuthority = value.match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i)?.[1];
78
+ return (url.protocol === "https:" || url.protocol === "http:") && !rawAuthority?.includes("@") && !url.username && !url.password && !url.search && !url.hash;
78
79
  } catch {
79
80
  return false;
80
81
  }
@@ -66,6 +66,8 @@ export interface PendingWebFlow {
66
66
  resourceOverrideSnapshot?: string | null;
67
67
  scope?: string;
68
68
  authorizationServerUrl: string;
69
+ /** Validated AS issuer frozen at start. Legacy rows without it cannot spend credentials. */
70
+ authorizationServerIssuer?: string;
69
71
  /** Persist as JSON (e.g. Postgres `jsonb`); contents are MCP SDK-typed. */
70
72
  clientInformation: OAuthClientInformation;
71
73
  createdAt: Date;
@@ -66,6 +66,8 @@ export interface PendingWebFlow {
66
66
  resourceOverrideSnapshot?: string | null;
67
67
  scope?: string;
68
68
  authorizationServerUrl: string;
69
+ /** Validated AS issuer frozen at start. Legacy rows without it cannot spend credentials. */
70
+ authorizationServerIssuer?: string;
69
71
  /** Persist as JSON (e.g. Postgres `jsonb`); contents are MCP SDK-typed. */
70
72
  clientInformation: OAuthClientInformation;
71
73
  createdAt: Date;
@@ -145,6 +145,8 @@ async function startWebOAuthFlow(opts) {
145
145
  throw new import_types.OAuthError(`No OAuth metadata at ${asUrl.toString()}`, "no_authorization_server_metadata", agent.id);
146
146
  }
147
147
  (0, import_types.assertOAuthServerIssuer)(asMetadata, asUrl.toString());
148
+ const authorizationServerIssuer = asUrl.toString();
149
+ assertSavedCredentialIssuers(agent, authorizationServerIssuer);
148
150
  const resource = resourceOverride ? new URL(resourceOverride) : prm?.resource ? new URL(prm.resource) : (0, import_auth_utils.resourceUrlFromServerUrl)(new URL(agent.agent_uri));
149
151
  const baseClientMetadata = {
150
152
  ...import_types.DEFAULT_CLIENT_METADATA,
@@ -159,7 +161,8 @@ async function startWebOAuthFlow(opts) {
159
161
  asMetadata,
160
162
  clientMetadata: baseClientMetadata,
161
163
  fetchFn: guardedFetch,
162
- allowConfidentialClient
164
+ allowConfidentialClient,
165
+ issuer: authorizationServerIssuer
163
166
  });
164
167
  const state = (generateState ?? defaultGenerateState)();
165
168
  const { authorizationUrl, codeVerifier } = await (0, import_auth.startAuthorization)(asUrl.toString(), {
@@ -187,6 +190,7 @@ async function startWebOAuthFlow(opts) {
187
190
  resourceOverrideSnapshot: agent.oauth_resource ?? null,
188
191
  scope,
189
192
  authorizationServerUrl: asUrl.toString(),
193
+ authorizationServerIssuer,
190
194
  clientInformation,
191
195
  createdAt: now,
192
196
  expiresAt,
@@ -232,11 +236,16 @@ async function completeWebOAuthFlow(opts) {
232
236
  fetchFn: guardedFetch
233
237
  });
234
238
  (0, import_types.assertOAuthServerIssuer)(asMetadata, flow.authorizationServerUrl);
239
+ assertFrozenIssuer(flow);
235
240
  (0, import_types.assertOAuthCredentialIssuer)(
236
241
  flow.clientInformation,
237
- flow.authorizationServerUrl,
242
+ flow.authorizationServerIssuer,
238
243
  !!flow.clientInformation.client_secret
239
244
  );
245
+ if (agentStorage) {
246
+ agentForPersistence = await agentStorage.loadAgent(flow.agentId);
247
+ assertAgentUnchangedForFlow(agentForPersistence, flow);
248
+ }
240
249
  let tokens;
241
250
  try {
242
251
  tokens = await (0, import_auth.exchangeAuthorization)(flow.authorizationServerUrl, {
@@ -248,7 +257,7 @@ async function completeWebOAuthFlow(opts) {
248
257
  resource: flow.resource ? new URL(flow.resource) : void 0,
249
258
  fetchFn: guardedFetch
250
259
  });
251
- tokens = { ...tokens, issuer: flow.authorizationServerUrl };
260
+ tokens = { ...tokens, issuer: flow.authorizationServerIssuer };
252
261
  } catch (err) {
253
262
  throw wrapTokenExchangeError(err);
254
263
  }
@@ -262,7 +271,8 @@ async function completeWebOAuthFlow(opts) {
262
271
  } else if (flow.resourceOverrideAction === "clear") {
263
272
  delete agentForPersistence.oauth_resource;
264
273
  }
265
- delete agentForPersistence.oauth_code_verifier;
274
+ agentForPersistence.oauth_code_verifier = void 0;
275
+ agentForPersistence.oauth_discovery_state = void 0;
266
276
  await agentStorage.saveAgent(agentForPersistence);
267
277
  persisted = true;
268
278
  }
@@ -274,6 +284,20 @@ async function completeWebOAuthFlow(opts) {
274
284
  persisted
275
285
  };
276
286
  }
287
+ function assertSavedCredentialIssuers(agent, issuer) {
288
+ if (agent.oauth_tokens) (0, import_types.assertOAuthCredentialIssuer)(agent.oauth_tokens, issuer, !!agent.oauth_tokens.refresh_token);
289
+ if (agent.oauth_client) (0, import_types.assertOAuthCredentialIssuer)(agent.oauth_client, issuer, !!agent.oauth_client.client_secret);
290
+ }
291
+ function assertFrozenIssuer(flow) {
292
+ if (!flow.authorizationServerIssuer) {
293
+ throw new import_types.OAuthError(
294
+ "Pending OAuth flow lacks a validated authorization-server issuer. Start a new owner sign-in.",
295
+ "oauth_issuer_required",
296
+ flow.agentId
297
+ );
298
+ }
299
+ (0, import_types.assertOAuthServerIssuer)({ issuer: flow.authorizationServerIssuer }, flow.authorizationServerUrl);
300
+ }
277
301
  function assertAgentUnchangedForFlow(agent, flow) {
278
302
  if (!agent) {
279
303
  throw new AgentVanishedDuringFlowError(flow.agentId);
@@ -281,6 +305,13 @@ function assertAgentUnchangedForFlow(agent, flow) {
281
305
  if (agent.agent_uri !== flow.agentUrl) {
282
306
  throw new AgentChangedDuringFlowError(flow.agentId);
283
307
  }
308
+ if (!agent.oauth_client) throw new AgentChangedDuringFlowError(flow.agentId);
309
+ const currentClient = (0, import_types.toMCPClientInfo)(agent.oauth_client);
310
+ for (const key of ["client_id", "client_secret", "client_secret_expires_at", "issuer"]) {
311
+ if (currentClient[key] !== flow.clientInformation[key]) throw new AgentChangedDuringFlowError(flow.agentId);
312
+ }
313
+ assertFrozenIssuer(flow);
314
+ assertSavedCredentialIssuers(agent, flow.authorizationServerIssuer);
284
315
  if (Object.prototype.hasOwnProperty.call(flow, "resourceOverrideSnapshot") && (agent.oauth_resource ?? null) !== flow.resourceOverrideSnapshot) {
285
316
  throw new AgentChangedDuringFlowError(flow.agentId);
286
317
  }
@@ -416,9 +447,8 @@ function createSsrfSafeOAuthFetch(trustedFetchFn, allowHttp) {
416
447
  });
417
448
  }
418
449
  async function resolveClientInformation(args) {
419
- const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient } = args;
450
+ const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient, issuer } = args;
420
451
  if (agent.oauth_client) {
421
- (0, import_types.assertOAuthCredentialIssuer)(agent.oauth_client, asUrl.toString(), !!agent.oauth_client.client_secret);
422
452
  return (0, import_types.toMCPClientInfo)(agent.oauth_client);
423
453
  }
424
454
  if (!asMetadata.registration_endpoint) {
@@ -428,17 +458,20 @@ async function resolveClientInformation(args) {
428
458
  agent.id
429
459
  );
430
460
  }
431
- const registration = await (0, import_auth.registerClient)(asUrl.toString(), {
461
+ const response = await (0, import_auth.registerClient)(asUrl.toString(), {
432
462
  metadata: asMetadata,
433
463
  clientMetadata,
434
464
  fetchFn
435
465
  });
436
- const registered = { ...registration, issuer: asUrl.toString() };
466
+ const registered = { ...response, issuer };
437
467
  if (registered.client_secret && !allowConfidentialClient) {
438
468
  throw new ConfidentialClientNotAllowedError(agent.id);
439
469
  }
440
470
  if (agentStorage) {
441
- const fresh = await agentStorage.loadAgent(agent.id) ?? agent;
471
+ const fresh = await agentStorage.loadAgent(agent.id);
472
+ if (!fresh) throw new AgentVanishedDuringFlowError(agent.id);
473
+ if (fresh.agent_uri !== agent.agent_uri || fresh.oauth_client) throw new AgentChangedDuringFlowError(agent.id);
474
+ assertSavedCredentialIssuers(fresh, issuer);
442
475
  fresh.oauth_client = (0, import_types.fromMCPClientInfo)(registered);
443
476
  await agentStorage.saveAgent(fresh);
444
477
  }