@adcp/sdk 14.3.0 → 15.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/bin/adcp.js +8 -5
  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 +25 -8
  5. package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +25 -8
  6. package/dist/lib/auth/oauth/MCPOAuthProvider.js +144 -19
  7. package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +157 -19
  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/diagnose.js +14 -0
  17. package/dist/lib/auth/oauth/diagnose.mjs +14 -0
  18. package/dist/lib/auth/oauth/file-storage.d.mts +4 -2
  19. package/dist/lib/auth/oauth/file-storage.d.ts +4 -2
  20. package/dist/lib/auth/oauth/file-storage.js +14 -1
  21. package/dist/lib/auth/oauth/file-storage.mjs +14 -1
  22. package/dist/lib/auth/oauth/index.d.mts +10 -6
  23. package/dist/lib/auth/oauth/index.d.ts +10 -6
  24. package/dist/lib/auth/oauth/index.js +9 -3
  25. package/dist/lib/auth/oauth/index.mjs +8 -3
  26. package/dist/lib/auth/oauth/types.d.mts +17 -0
  27. package/dist/lib/auth/oauth/types.d.ts +17 -0
  28. package/dist/lib/auth/oauth/types.js +40 -0
  29. package/dist/lib/auth/oauth/types.mjs +38 -0
  30. package/dist/lib/auth/oauth/web-flow.d.mts +2 -0
  31. package/dist/lib/auth/oauth/web-flow.d.ts +2 -0
  32. package/dist/lib/auth/oauth/web-flow.js +48 -5
  33. package/dist/lib/auth/oauth/web-flow.mjs +57 -6
  34. package/dist/lib/index.d.mts +1 -1
  35. package/dist/lib/index.d.ts +1 -1
  36. package/dist/lib/index.js +2 -0
  37. package/dist/lib/index.mjs +3 -1
  38. package/dist/lib/protocols/mcp.js +2 -0
  39. package/dist/lib/protocols/mcp.mjs +2 -0
  40. package/dist/lib/schemas/index.d.mts +2 -0
  41. package/dist/lib/schemas/index.d.ts +2 -0
  42. package/dist/lib/schemas/index.js +3 -0
  43. package/dist/lib/schemas/index.mjs +2 -0
  44. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  45. package/dist/lib/testing/client.js +8 -3
  46. package/dist/lib/testing/client.mjs +8 -3
  47. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  48. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  49. package/dist/lib/testing/types.d.mts +10 -1
  50. package/dist/lib/testing/types.d.ts +10 -1
  51. package/dist/lib/types/adcp.d.mts +6 -0
  52. package/dist/lib/types/adcp.d.ts +6 -0
  53. package/dist/lib/version.d.mts +3 -3
  54. package/dist/lib/version.d.ts +3 -3
  55. package/dist/lib/version.js +3 -3
  56. package/dist/lib/version.mjs +3 -3
  57. package/docs/TYPE-SUMMARY.md +1 -1
  58. package/docs/ZOD-SCHEMAS.md +19 -0
  59. package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
  60. package/docs/guides/WEB-OAUTH.md +58 -163
  61. package/docs/guides/account-resolution.md +1 -1
  62. package/docs/llms.txt +1 -1
  63. package/docs/migration-14.0-to-14.1.md +1 -1
  64. package/docs/migration-14.x-rc-worksheet.md +8 -8
  65. package/docs/migration-oauth-issuer-bindings.md +17 -0
  66. package/package.json +5 -5
@@ -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,6 +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
+ * 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.
16
20
  * This allows different storage backends (file, database, memory)
17
21
  * while keeping tokens in the agent config structure.
18
22
  */
@@ -74,6 +78,11 @@ export interface OAuthProviderConfig {
74
78
  storage?: OAuthConfigStorage;
75
79
  /** Authorization flow handler */
76
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;
77
86
  /** OAuth client metadata (required - use DEFAULT_CLIENT_METADATA as base) */
78
87
  clientMetadata: OAuthClientMetadata;
79
88
  /**
@@ -132,3 +141,11 @@ export declare function toMCPClientInfo(client: AgentOAuthClient): OAuthClientIn
132
141
  * Convert MCP SDK OAuthClientInformationFull to AgentOAuthClient format
133
142
  */
134
143
  export declare function fromMCPClientInfo(info: OAuthClientInformationFull): AgentOAuthClient;
144
+ /** Internal credential-binding guard; old secrets must be migrated from trusted configuration. */
145
+ export declare function assertOAuthCredentialIssuer(credential: {
146
+ issuer?: unknown;
147
+ }, expectedIssuer?: string, requireIssuer?: boolean): void;
148
+ /** Internal metadata guard, matching the modern MCP SDK's asymmetric issuer-echo rule. */
149
+ export declare function assertOAuthServerIssuer(metadata: {
150
+ issuer?: unknown;
151
+ } | undefined, authorizationServerUrl: string): void;
@@ -13,6 +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
+ * 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.
16
20
  * This allows different storage backends (file, database, memory)
17
21
  * while keeping tokens in the agent config structure.
18
22
  */
@@ -74,6 +78,11 @@ export interface OAuthProviderConfig {
74
78
  storage?: OAuthConfigStorage;
75
79
  /** Authorization flow handler */
76
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;
77
86
  /** OAuth client metadata (required - use DEFAULT_CLIENT_METADATA as base) */
78
87
  clientMetadata: OAuthClientMetadata;
79
88
  /**
@@ -132,3 +141,11 @@ export declare function toMCPClientInfo(client: AgentOAuthClient): OAuthClientIn
132
141
  * Convert MCP SDK OAuthClientInformationFull to AgentOAuthClient format
133
142
  */
134
143
  export declare function fromMCPClientInfo(info: OAuthClientInformationFull): AgentOAuthClient;
144
+ /** Internal credential-binding guard; old secrets must be migrated from trusted configuration. */
145
+ export declare function assertOAuthCredentialIssuer(credential: {
146
+ issuer?: unknown;
147
+ }, expectedIssuer?: string, requireIssuer?: boolean): void;
148
+ /** Internal metadata guard, matching the modern MCP SDK's asymmetric issuer-echo rule. */
149
+ export declare function assertOAuthServerIssuer(metadata: {
150
+ issuer?: unknown;
151
+ } | undefined, authorizationServerUrl: string): void;
@@ -22,6 +22,8 @@ __export(types_exports, {
22
22
  OAuthCancelledError: () => OAuthCancelledError,
23
23
  OAuthError: () => OAuthError,
24
24
  OAuthTimeoutError: () => OAuthTimeoutError,
25
+ assertOAuthCredentialIssuer: () => assertOAuthCredentialIssuer,
26
+ assertOAuthServerIssuer: () => assertOAuthServerIssuer,
25
27
  fromMCPClientInfo: () => fromMCPClientInfo,
26
28
  fromMCPTokens: () => fromMCPTokens,
27
29
  toMCPClientInfo: () => toMCPClientInfo,
@@ -60,6 +62,7 @@ class OAuthTimeoutError extends OAuthError {
60
62
  }
61
63
  function toMCPTokens(tokens) {
62
64
  return {
65
+ ...tokens.issuer !== void 0 && tokens.issuer !== null && { issuer: tokens.issuer },
63
66
  access_token: tokens.access_token,
64
67
  refresh_token: tokens.refresh_token,
65
68
  token_type: tokens.token_type || "Bearer",
@@ -69,6 +72,7 @@ function toMCPTokens(tokens) {
69
72
  }
70
73
  function fromMCPTokens(tokens) {
71
74
  const result = {
75
+ ...tokens.issuer !== void 0 && tokens.issuer !== null && { issuer: tokens.issuer },
72
76
  access_token: tokens.access_token,
73
77
  token_type: tokens.token_type,
74
78
  scope: tokens.scope
@@ -84,6 +88,7 @@ function fromMCPTokens(tokens) {
84
88
  }
85
89
  function toMCPClientInfo(client) {
86
90
  return {
91
+ ...client.issuer !== void 0 && client.issuer !== null && { issuer: client.issuer },
87
92
  client_id: client.client_id,
88
93
  client_secret: client.client_secret,
89
94
  client_secret_expires_at: client.client_secret_expires_at
@@ -91,17 +96,52 @@ function toMCPClientInfo(client) {
91
96
  }
92
97
  function fromMCPClientInfo(info) {
93
98
  return {
99
+ ...info.issuer !== void 0 && info.issuer !== null && { issuer: info.issuer },
94
100
  client_id: info.client_id,
95
101
  client_secret: info.client_secret,
96
102
  client_secret_expires_at: info.client_secret_expires_at
97
103
  };
98
104
  }
105
+ function isOAuthIssuer(value) {
106
+ if (typeof value !== "string" || !value || /[\u0000-\u0020\u007f\\]/.test(value)) return false;
107
+ try {
108
+ const url = new URL(value);
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;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ function assertOAuthCredentialIssuer(credential, expectedIssuer, requireIssuer = false) {
116
+ const issuer = credential.issuer;
117
+ if ((issuer === void 0 || issuer === null) && !requireIssuer) return;
118
+ if (!isOAuthIssuer(issuer)) {
119
+ throw new OAuthError(
120
+ "Saved OAuth credentials need a valid issuer. Clear them and sign in again, or set their issuer from trusted configuration.",
121
+ "oauth_issuer_required"
122
+ );
123
+ }
124
+ if (expectedIssuer !== void 0 && issuer !== expectedIssuer && !(issuer.endsWith("/") && issuer.slice(0, -1) === expectedIssuer) && !(expectedIssuer.endsWith("/") && expectedIssuer.slice(0, -1) === issuer)) {
125
+ throw new OAuthError("OAuth credentials belong to a different authorization server.", "oauth_issuer_mismatch");
126
+ }
127
+ }
128
+ function assertOAuthServerIssuer(metadata, authorizationServerUrl) {
129
+ const issuer = metadata?.issuer;
130
+ if (!isOAuthIssuer(issuer) || issuer !== authorizationServerUrl && !(authorizationServerUrl.endsWith("/") && issuer === authorizationServerUrl.slice(0, -1))) {
131
+ throw new OAuthError(
132
+ "Authorization-server metadata does not match the discovered issuer.",
133
+ "oauth_issuer_mismatch"
134
+ );
135
+ }
136
+ }
99
137
  // Annotate the CommonJS export names for ESM import in node:
100
138
  0 && (module.exports = {
101
139
  DEFAULT_CLIENT_METADATA,
102
140
  OAuthCancelledError,
103
141
  OAuthError,
104
142
  OAuthTimeoutError,
143
+ assertOAuthCredentialIssuer,
144
+ assertOAuthServerIssuer,
105
145
  fromMCPClientInfo,
106
146
  fromMCPTokens,
107
147
  toMCPClientInfo,
@@ -30,6 +30,7 @@ class OAuthTimeoutError extends OAuthError {
30
30
  }
31
31
  function toMCPTokens(tokens) {
32
32
  return {
33
+ ...tokens.issuer !== void 0 && tokens.issuer !== null && { issuer: tokens.issuer },
33
34
  access_token: tokens.access_token,
34
35
  refresh_token: tokens.refresh_token,
35
36
  token_type: tokens.token_type || "Bearer",
@@ -39,6 +40,7 @@ function toMCPTokens(tokens) {
39
40
  }
40
41
  function fromMCPTokens(tokens) {
41
42
  const result = {
43
+ ...tokens.issuer !== void 0 && tokens.issuer !== null && { issuer: tokens.issuer },
42
44
  access_token: tokens.access_token,
43
45
  token_type: tokens.token_type,
44
46
  scope: tokens.scope
@@ -54,6 +56,7 @@ function fromMCPTokens(tokens) {
54
56
  }
55
57
  function toMCPClientInfo(client) {
56
58
  return {
59
+ ...client.issuer !== void 0 && client.issuer !== null && { issuer: client.issuer },
57
60
  client_id: client.client_id,
58
61
  client_secret: client.client_secret,
59
62
  client_secret_expires_at: client.client_secret_expires_at
@@ -61,16 +64,51 @@ function toMCPClientInfo(client) {
61
64
  }
62
65
  function fromMCPClientInfo(info) {
63
66
  return {
67
+ ...info.issuer !== void 0 && info.issuer !== null && { issuer: info.issuer },
64
68
  client_id: info.client_id,
65
69
  client_secret: info.client_secret,
66
70
  client_secret_expires_at: info.client_secret_expires_at
67
71
  };
68
72
  }
73
+ function isOAuthIssuer(value) {
74
+ if (typeof value !== "string" || !value || /[\u0000-\u0020\u007f\\]/.test(value)) return false;
75
+ try {
76
+ const url = new URL(value);
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;
79
+ } catch {
80
+ return false;
81
+ }
82
+ }
83
+ function assertOAuthCredentialIssuer(credential, expectedIssuer, requireIssuer = false) {
84
+ const issuer = credential.issuer;
85
+ if ((issuer === void 0 || issuer === null) && !requireIssuer) return;
86
+ if (!isOAuthIssuer(issuer)) {
87
+ throw new OAuthError(
88
+ "Saved OAuth credentials need a valid issuer. Clear them and sign in again, or set their issuer from trusted configuration.",
89
+ "oauth_issuer_required"
90
+ );
91
+ }
92
+ if (expectedIssuer !== void 0 && issuer !== expectedIssuer && !(issuer.endsWith("/") && issuer.slice(0, -1) === expectedIssuer) && !(expectedIssuer.endsWith("/") && expectedIssuer.slice(0, -1) === issuer)) {
93
+ throw new OAuthError("OAuth credentials belong to a different authorization server.", "oauth_issuer_mismatch");
94
+ }
95
+ }
96
+ function assertOAuthServerIssuer(metadata, authorizationServerUrl) {
97
+ const issuer = metadata?.issuer;
98
+ if (!isOAuthIssuer(issuer) || issuer !== authorizationServerUrl && !(authorizationServerUrl.endsWith("/") && issuer === authorizationServerUrl.slice(0, -1))) {
99
+ throw new OAuthError(
100
+ "Authorization-server metadata does not match the discovered issuer.",
101
+ "oauth_issuer_mismatch"
102
+ );
103
+ }
104
+ }
69
105
  export {
70
106
  DEFAULT_CLIENT_METADATA,
71
107
  OAuthCancelledError,
72
108
  OAuthError,
73
109
  OAuthTimeoutError,
110
+ assertOAuthCredentialIssuer,
111
+ assertOAuthServerIssuer,
74
112
  fromMCPClientInfo,
75
113
  fromMCPTokens,
76
114
  toMCPClientInfo,
@@ -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;
@@ -144,6 +144,9 @@ async function startWebOAuthFlow(opts) {
144
144
  if (!asMetadata) {
145
145
  throw new import_types.OAuthError(`No OAuth metadata at ${asUrl.toString()}`, "no_authorization_server_metadata", agent.id);
146
146
  }
147
+ (0, import_types.assertOAuthServerIssuer)(asMetadata, asUrl.toString());
148
+ const authorizationServerIssuer = asUrl.toString();
149
+ assertSavedCredentialIssuers(agent, authorizationServerIssuer);
147
150
  const resource = resourceOverride ? new URL(resourceOverride) : prm?.resource ? new URL(prm.resource) : (0, import_auth_utils.resourceUrlFromServerUrl)(new URL(agent.agent_uri));
148
151
  const baseClientMetadata = {
149
152
  ...import_types.DEFAULT_CLIENT_METADATA,
@@ -158,7 +161,8 @@ async function startWebOAuthFlow(opts) {
158
161
  asMetadata,
159
162
  clientMetadata: baseClientMetadata,
160
163
  fetchFn: guardedFetch,
161
- allowConfidentialClient
164
+ allowConfidentialClient,
165
+ issuer: authorizationServerIssuer
162
166
  });
163
167
  const state = (generateState ?? defaultGenerateState)();
164
168
  const { authorizationUrl, codeVerifier } = await (0, import_auth.startAuthorization)(asUrl.toString(), {
@@ -186,6 +190,7 @@ async function startWebOAuthFlow(opts) {
186
190
  resourceOverrideSnapshot: agent.oauth_resource ?? null,
187
191
  scope,
188
192
  authorizationServerUrl: asUrl.toString(),
193
+ authorizationServerIssuer,
189
194
  clientInformation,
190
195
  createdAt: now,
191
196
  expiresAt,
@@ -230,6 +235,17 @@ async function completeWebOAuthFlow(opts) {
230
235
  const asMetadata = await (0, import_auth.discoverAuthorizationServerMetadata)(flow.authorizationServerUrl, {
231
236
  fetchFn: guardedFetch
232
237
  });
238
+ (0, import_types.assertOAuthServerIssuer)(asMetadata, flow.authorizationServerUrl);
239
+ assertFrozenIssuer(flow);
240
+ (0, import_types.assertOAuthCredentialIssuer)(
241
+ flow.clientInformation,
242
+ flow.authorizationServerIssuer,
243
+ !!flow.clientInformation.client_secret
244
+ );
245
+ if (agentStorage) {
246
+ agentForPersistence = await agentStorage.loadAgent(flow.agentId);
247
+ assertAgentUnchangedForFlow(agentForPersistence, flow);
248
+ }
233
249
  let tokens;
234
250
  try {
235
251
  tokens = await (0, import_auth.exchangeAuthorization)(flow.authorizationServerUrl, {
@@ -241,6 +257,7 @@ async function completeWebOAuthFlow(opts) {
241
257
  resource: flow.resource ? new URL(flow.resource) : void 0,
242
258
  fetchFn: guardedFetch
243
259
  });
260
+ tokens = { ...tokens, issuer: flow.authorizationServerIssuer };
244
261
  } catch (err) {
245
262
  throw wrapTokenExchangeError(err);
246
263
  }
@@ -254,7 +271,8 @@ async function completeWebOAuthFlow(opts) {
254
271
  } else if (flow.resourceOverrideAction === "clear") {
255
272
  delete agentForPersistence.oauth_resource;
256
273
  }
257
- delete agentForPersistence.oauth_code_verifier;
274
+ agentForPersistence.oauth_code_verifier = void 0;
275
+ agentForPersistence.oauth_discovery_state = void 0;
258
276
  await agentStorage.saveAgent(agentForPersistence);
259
277
  persisted = true;
260
278
  }
@@ -266,6 +284,20 @@ async function completeWebOAuthFlow(opts) {
266
284
  persisted
267
285
  };
268
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
+ }
269
301
  function assertAgentUnchangedForFlow(agent, flow) {
270
302
  if (!agent) {
271
303
  throw new AgentVanishedDuringFlowError(flow.agentId);
@@ -273,6 +305,13 @@ function assertAgentUnchangedForFlow(agent, flow) {
273
305
  if (agent.agent_uri !== flow.agentUrl) {
274
306
  throw new AgentChangedDuringFlowError(flow.agentId);
275
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);
276
315
  if (Object.prototype.hasOwnProperty.call(flow, "resourceOverrideSnapshot") && (agent.oauth_resource ?? null) !== flow.resourceOverrideSnapshot) {
277
316
  throw new AgentChangedDuringFlowError(flow.agentId);
278
317
  }
@@ -408,7 +447,7 @@ function createSsrfSafeOAuthFetch(trustedFetchFn, allowHttp) {
408
447
  });
409
448
  }
410
449
  async function resolveClientInformation(args) {
411
- const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient } = args;
450
+ const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient, issuer } = args;
412
451
  if (agent.oauth_client) {
413
452
  return (0, import_types.toMCPClientInfo)(agent.oauth_client);
414
453
  }
@@ -419,16 +458,20 @@ async function resolveClientInformation(args) {
419
458
  agent.id
420
459
  );
421
460
  }
422
- const registered = await (0, import_auth.registerClient)(asUrl.toString(), {
461
+ const response = await (0, import_auth.registerClient)(asUrl.toString(), {
423
462
  metadata: asMetadata,
424
463
  clientMetadata,
425
464
  fetchFn
426
465
  });
466
+ const registered = { ...response, issuer };
427
467
  if (registered.client_secret && !allowConfidentialClient) {
428
468
  throw new ConfidentialClientNotAllowedError(agent.id);
429
469
  }
430
470
  if (agentStorage) {
431
- 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);
432
475
  fresh.oauth_client = (0, import_types.fromMCPClientInfo)(registered);
433
476
  await agentStorage.saveAgent(fresh);
434
477
  }
@@ -7,7 +7,15 @@ import {
7
7
  startAuthorization
8
8
  } from "@modelcontextprotocol/sdk/client/auth.js";
9
9
  import { checkResourceAllowed, resourceUrlFromServerUrl } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
10
- import { DEFAULT_CLIENT_METADATA, fromMCPClientInfo, fromMCPTokens, OAuthError, toMCPClientInfo } from "./types.mjs";
10
+ import {
11
+ DEFAULT_CLIENT_METADATA,
12
+ fromMCPClientInfo,
13
+ fromMCPTokens,
14
+ OAuthError,
15
+ toMCPClientInfo,
16
+ assertOAuthCredentialIssuer,
17
+ assertOAuthServerIssuer
18
+ } from "./types.mjs";
11
19
  import { validateOAuthResourceUrl } from "./resource-url.mjs";
12
20
  import { ssrfSafeFetch } from "../../net/ssrf-fetch.mjs";
13
21
  const DEFAULT_WEB_FLOW_TTL_MS = 10 * 60 * 1e3;
@@ -115,6 +123,9 @@ async function startWebOAuthFlow(opts) {
115
123
  if (!asMetadata) {
116
124
  throw new OAuthError(`No OAuth metadata at ${asUrl.toString()}`, "no_authorization_server_metadata", agent.id);
117
125
  }
126
+ assertOAuthServerIssuer(asMetadata, asUrl.toString());
127
+ const authorizationServerIssuer = asUrl.toString();
128
+ assertSavedCredentialIssuers(agent, authorizationServerIssuer);
118
129
  const resource = resourceOverride ? new URL(resourceOverride) : prm?.resource ? new URL(prm.resource) : resourceUrlFromServerUrl(new URL(agent.agent_uri));
119
130
  const baseClientMetadata = {
120
131
  ...DEFAULT_CLIENT_METADATA,
@@ -129,7 +140,8 @@ async function startWebOAuthFlow(opts) {
129
140
  asMetadata,
130
141
  clientMetadata: baseClientMetadata,
131
142
  fetchFn: guardedFetch,
132
- allowConfidentialClient
143
+ allowConfidentialClient,
144
+ issuer: authorizationServerIssuer
133
145
  });
134
146
  const state = (generateState ?? defaultGenerateState)();
135
147
  const { authorizationUrl, codeVerifier } = await startAuthorization(asUrl.toString(), {
@@ -157,6 +169,7 @@ async function startWebOAuthFlow(opts) {
157
169
  resourceOverrideSnapshot: agent.oauth_resource ?? null,
158
170
  scope,
159
171
  authorizationServerUrl: asUrl.toString(),
172
+ authorizationServerIssuer,
160
173
  clientInformation,
161
174
  createdAt: now,
162
175
  expiresAt,
@@ -201,6 +214,17 @@ async function completeWebOAuthFlow(opts) {
201
214
  const asMetadata = await discoverAuthorizationServerMetadata(flow.authorizationServerUrl, {
202
215
  fetchFn: guardedFetch
203
216
  });
217
+ assertOAuthServerIssuer(asMetadata, flow.authorizationServerUrl);
218
+ assertFrozenIssuer(flow);
219
+ assertOAuthCredentialIssuer(
220
+ flow.clientInformation,
221
+ flow.authorizationServerIssuer,
222
+ !!flow.clientInformation.client_secret
223
+ );
224
+ if (agentStorage) {
225
+ agentForPersistence = await agentStorage.loadAgent(flow.agentId);
226
+ assertAgentUnchangedForFlow(agentForPersistence, flow);
227
+ }
204
228
  let tokens;
205
229
  try {
206
230
  tokens = await exchangeAuthorization(flow.authorizationServerUrl, {
@@ -212,6 +236,7 @@ async function completeWebOAuthFlow(opts) {
212
236
  resource: flow.resource ? new URL(flow.resource) : void 0,
213
237
  fetchFn: guardedFetch
214
238
  });
239
+ tokens = { ...tokens, issuer: flow.authorizationServerIssuer };
215
240
  } catch (err) {
216
241
  throw wrapTokenExchangeError(err);
217
242
  }
@@ -225,7 +250,8 @@ async function completeWebOAuthFlow(opts) {
225
250
  } else if (flow.resourceOverrideAction === "clear") {
226
251
  delete agentForPersistence.oauth_resource;
227
252
  }
228
- delete agentForPersistence.oauth_code_verifier;
253
+ agentForPersistence.oauth_code_verifier = void 0;
254
+ agentForPersistence.oauth_discovery_state = void 0;
229
255
  await agentStorage.saveAgent(agentForPersistence);
230
256
  persisted = true;
231
257
  }
@@ -237,6 +263,20 @@ async function completeWebOAuthFlow(opts) {
237
263
  persisted
238
264
  };
239
265
  }
266
+ function assertSavedCredentialIssuers(agent, issuer) {
267
+ if (agent.oauth_tokens) assertOAuthCredentialIssuer(agent.oauth_tokens, issuer, !!agent.oauth_tokens.refresh_token);
268
+ if (agent.oauth_client) assertOAuthCredentialIssuer(agent.oauth_client, issuer, !!agent.oauth_client.client_secret);
269
+ }
270
+ function assertFrozenIssuer(flow) {
271
+ if (!flow.authorizationServerIssuer) {
272
+ throw new OAuthError(
273
+ "Pending OAuth flow lacks a validated authorization-server issuer. Start a new owner sign-in.",
274
+ "oauth_issuer_required",
275
+ flow.agentId
276
+ );
277
+ }
278
+ assertOAuthServerIssuer({ issuer: flow.authorizationServerIssuer }, flow.authorizationServerUrl);
279
+ }
240
280
  function assertAgentUnchangedForFlow(agent, flow) {
241
281
  if (!agent) {
242
282
  throw new AgentVanishedDuringFlowError(flow.agentId);
@@ -244,6 +284,13 @@ function assertAgentUnchangedForFlow(agent, flow) {
244
284
  if (agent.agent_uri !== flow.agentUrl) {
245
285
  throw new AgentChangedDuringFlowError(flow.agentId);
246
286
  }
287
+ if (!agent.oauth_client) throw new AgentChangedDuringFlowError(flow.agentId);
288
+ const currentClient = toMCPClientInfo(agent.oauth_client);
289
+ for (const key of ["client_id", "client_secret", "client_secret_expires_at", "issuer"]) {
290
+ if (currentClient[key] !== flow.clientInformation[key]) throw new AgentChangedDuringFlowError(flow.agentId);
291
+ }
292
+ assertFrozenIssuer(flow);
293
+ assertSavedCredentialIssuers(agent, flow.authorizationServerIssuer);
247
294
  if (Object.prototype.hasOwnProperty.call(flow, "resourceOverrideSnapshot") && (agent.oauth_resource ?? null) !== flow.resourceOverrideSnapshot) {
248
295
  throw new AgentChangedDuringFlowError(flow.agentId);
249
296
  }
@@ -379,7 +426,7 @@ function createSsrfSafeOAuthFetch(trustedFetchFn, allowHttp) {
379
426
  });
380
427
  }
381
428
  async function resolveClientInformation(args) {
382
- const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient } = args;
429
+ const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient, issuer } = args;
383
430
  if (agent.oauth_client) {
384
431
  return toMCPClientInfo(agent.oauth_client);
385
432
  }
@@ -390,16 +437,20 @@ async function resolveClientInformation(args) {
390
437
  agent.id
391
438
  );
392
439
  }
393
- const registered = await registerClient(asUrl.toString(), {
440
+ const response = await registerClient(asUrl.toString(), {
394
441
  metadata: asMetadata,
395
442
  clientMetadata,
396
443
  fetchFn
397
444
  });
445
+ const registered = { ...response, issuer };
398
446
  if (registered.client_secret && !allowConfidentialClient) {
399
447
  throw new ConfidentialClientNotAllowedError(agent.id);
400
448
  }
401
449
  if (agentStorage) {
402
- const fresh = await agentStorage.loadAgent(agent.id) ?? agent;
450
+ const fresh = await agentStorage.loadAgent(agent.id);
451
+ if (!fresh) throw new AgentVanishedDuringFlowError(agent.id);
452
+ if (fresh.agent_uri !== agent.agent_uri || fresh.oauth_client) throw new AgentChangedDuringFlowError(agent.id);
453
+ assertSavedCredentialIssuers(fresh, issuer);
403
454
  fresh.oauth_client = fromMCPClientInfo(registered);
404
455
  await agentStorage.saveAgent(fresh);
405
456
  }
@@ -186,7 +186,7 @@ export { getAuthToken, createAdCPHeaders, createMCPAuthHeaders, createAuthentica
186
186
  export { parseWWWAuthenticate, decodeAccessTokenClaims, validateTokenAudience, type WWWAuthenticateChallenge, type DecodedJWTHeader, type DecodedJWTClaims, type DecodedAccessToken, type TokenAudienceResult, InvalidTokenError, InsufficientScopeError, } from './auth/oauth/index.mjs';
187
187
  export { runAuthDiagnosis, AUTH_DIAGNOSIS_SCHEMA_VERSION, type DiagnoseOptions, type HttpCapture as DiagnosisHttpCapture, type DiagnosisStep, type Hypothesis as DiagnosisHypothesis, type HypothesisVerdict as DiagnosisHypothesisVerdict, type AuthDiagnosisReport, } from './auth/oauth/index.mjs';
188
188
  export { NeedsAuthorizationError, discoverAuthorizationRequirements, hasValidatedMcpAuthorizationRequirements, createFileOAuthStorage, bindAgentStorage, getAgentStorage, unbindAgentStorage, type AuthorizationRequirements, type DiscoverAuthorizationOptions, type FileOAuthStorageOptions, } from './auth/oauth/index.mjs';
189
- export { exchangeClientCredentials, ensureClientCredentialsTokens, ClientCredentialsExchangeError, MissingEnvSecretError, resolveSecret, isEnvSecretReference, toEnvSecretReference, extractEnvSecretName, type ExchangeClientCredentialsOptions, type EnsureClientCredentialsOptions, type AgentOAuthClientCredentials, } from './auth/oauth/index.mjs';
189
+ export { exchangeClientCredentials, ensureClientCredentialsTokens, ClientCredentialsExchangeError, MissingEnvSecretError, resolveSecret, isEnvSecretReference, toEnvSecretReference, extractEnvSecretName, type ExchangeClientCredentialsOptions, type EnsureClientCredentialsOptions, type AgentOAuthClientCredentials, AgentOAuthClientCredentialsSchema, } from './auth/oauth/index.mjs';
190
190
  export { validateAgentUrl, validateAdCPResponse, getExpectedSchema, handleAdCPResponse } from './validation/index.mjs';
191
191
  export { ProtocolClient, callMCPTool, callA2ATool, createA2AClientFromCardUrl, createMCPClient, createA2AClient, closeMCPConnections, closeOAuthConnections, bundleSupportsAdcpVersionField, BODY_SNIPPET_TIMEOUT_MS, OBSERVER_FLUSH_TIMEOUT_MS, sanitizeTransportHeaders, sanitizeTransportUrl, } from './protocols/index.mjs';
192
192
  export { toReleasePrecisionWire, validateAdcpVersionWire } from './validation/schema-loader.mjs';
@@ -186,7 +186,7 @@ export { getAuthToken, createAdCPHeaders, createMCPAuthHeaders, createAuthentica
186
186
  export { parseWWWAuthenticate, decodeAccessTokenClaims, validateTokenAudience, type WWWAuthenticateChallenge, type DecodedJWTHeader, type DecodedJWTClaims, type DecodedAccessToken, type TokenAudienceResult, InvalidTokenError, InsufficientScopeError, } from './auth/oauth';
187
187
  export { runAuthDiagnosis, AUTH_DIAGNOSIS_SCHEMA_VERSION, type DiagnoseOptions, type HttpCapture as DiagnosisHttpCapture, type DiagnosisStep, type Hypothesis as DiagnosisHypothesis, type HypothesisVerdict as DiagnosisHypothesisVerdict, type AuthDiagnosisReport, } from './auth/oauth';
188
188
  export { NeedsAuthorizationError, discoverAuthorizationRequirements, hasValidatedMcpAuthorizationRequirements, createFileOAuthStorage, bindAgentStorage, getAgentStorage, unbindAgentStorage, type AuthorizationRequirements, type DiscoverAuthorizationOptions, type FileOAuthStorageOptions, } from './auth/oauth';
189
- export { exchangeClientCredentials, ensureClientCredentialsTokens, ClientCredentialsExchangeError, MissingEnvSecretError, resolveSecret, isEnvSecretReference, toEnvSecretReference, extractEnvSecretName, type ExchangeClientCredentialsOptions, type EnsureClientCredentialsOptions, type AgentOAuthClientCredentials, } from './auth/oauth';
189
+ export { exchangeClientCredentials, ensureClientCredentialsTokens, ClientCredentialsExchangeError, MissingEnvSecretError, resolveSecret, isEnvSecretReference, toEnvSecretReference, extractEnvSecretName, type ExchangeClientCredentialsOptions, type EnsureClientCredentialsOptions, type AgentOAuthClientCredentials, AgentOAuthClientCredentialsSchema, } from './auth/oauth';
190
190
  export { validateAgentUrl, validateAdCPResponse, getExpectedSchema, handleAdCPResponse } from './validation';
191
191
  export { ProtocolClient, callMCPTool, callA2ATool, createA2AClientFromCardUrl, createMCPClient, createA2AClient, closeMCPConnections, closeOAuthConnections, bundleSupportsAdcpVersionField, BODY_SNIPPET_TIMEOUT_MS, OBSERVER_FLUSH_TIMEOUT_MS, sanitizeTransportHeaders, sanitizeTransportUrl, } from './protocols';
192
192
  export { toReleasePrecisionWire, validateAdcpVersionWire } from './validation/schema-loader';
package/dist/lib/index.js CHANGED
@@ -48,6 +48,7 @@ __export(index_exports, {
48
48
  AdagentsJsonSchema: () => import_wellknown_schemas.AdagentsJsonSchema,
49
49
  AgentClient: () => import_AgentClient.AgentClient,
50
50
  AgentNotFoundError: () => import_errors.AgentNotFoundError,
51
+ AgentOAuthClientCredentialsSchema: () => import_oauth4.AgentOAuthClientCredentialsSchema,
51
52
  Asset: () => import_asset_builders.Asset,
52
53
  AsyncHandler: () => import_AsyncHandler.AsyncHandler,
53
54
  AuthenticationCredentialsRejectedError: () => import_errors.AuthenticationCredentialsRejectedError,
@@ -888,6 +889,7 @@ function createAdCPClientFromEnv() {
888
889
  AdagentsJsonSchema,
889
890
  AgentClient,
890
891
  AgentNotFoundError,
892
+ AgentOAuthClientCredentialsSchema,
891
893
  Asset,
892
894
  AsyncHandler,
893
895
  AuthenticationCredentialsRejectedError,