@ai-sdk/mcp 1.0.80 → 1.0.82

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/mcp",
3
- "version": "1.0.80",
3
+ "version": "1.0.82",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -12,6 +12,7 @@ import {
12
12
  extractWWWAuthenticateParams,
13
13
  UnauthorizedError,
14
14
  auth,
15
+ type AuthResult,
15
16
  type OAuthClientProvider,
16
17
  } from './oauth';
17
18
  import { LATEST_PROTOCOL_VERSION } from './types';
@@ -33,6 +34,7 @@ export class SseMCPTransport implements MCPTransport {
33
34
  private resourceMetadataUrl?: URL;
34
35
  private redirectMode: RequestRedirect;
35
36
  private fetchFn: FetchFunction;
37
+ private authPromise?: Promise<AuthResult>;
36
38
 
37
39
  onclose?: () => void;
38
40
  onerror?: (error: unknown) => void;
@@ -86,6 +88,40 @@ export class SseMCPTransport implements MCPTransport {
86
88
  );
87
89
  }
88
90
 
91
+ /**
92
+ * Runs a single OAuth recovery flow for concurrent 401 responses.
93
+ */
94
+ private authorizeOnce(
95
+ resourceMetadataUrl?: URL,
96
+ scope?: string,
97
+ ): Promise<AuthResult> {
98
+ if (!this.authProvider) {
99
+ return Promise.resolve('REDIRECT');
100
+ }
101
+
102
+ if (!this.authPromise) {
103
+ this.authPromise = auth(this.authProvider, {
104
+ serverUrl: this.url,
105
+ resourceMetadataUrl,
106
+ scope,
107
+ fetchFn: this.fetchFn,
108
+ }).finally(() => {
109
+ this.authPromise = undefined;
110
+ });
111
+ }
112
+
113
+ return this.authPromise;
114
+ }
115
+
116
+ private async accessTokenChanged(
117
+ requestAuthorization: string | undefined,
118
+ ): Promise<boolean> {
119
+ const accessToken = (await this.authProvider?.tokens())?.access_token;
120
+ return (
121
+ accessToken != null && requestAuthorization !== `Bearer ${accessToken}`
122
+ );
123
+ }
124
+
89
125
  async start(): Promise<void> {
90
126
  return new Promise<void>((resolve, reject) => {
91
127
  if (this.connected) {
@@ -110,12 +146,10 @@ export class SseMCPTransport implements MCPTransport {
110
146
  extractWWWAuthenticateParams(response);
111
147
  this.resourceMetadataUrl = resourceMetadataUrl;
112
148
  try {
113
- const result = await auth(this.authProvider, {
114
- serverUrl: this.url,
115
- resourceMetadataUrl: this.resourceMetadataUrl,
149
+ const result = await this.authorizeOnce(
150
+ this.resourceMetadataUrl,
116
151
  scope,
117
- fetchFn: this.fetchFn,
118
- });
152
+ );
119
153
  if (result !== 'AUTHORIZED') {
120
154
  const error = new UnauthorizedError();
121
155
  this.onerror?.(error);
@@ -276,15 +310,21 @@ export class SseMCPTransport implements MCPTransport {
276
310
  const response = await this.fetchFn(endpoint.href, init);
277
311
 
278
312
  if (response.status === 401 && this.authProvider && !triedAuth) {
313
+ if (
314
+ await this.accessTokenChanged(
315
+ new Headers(headers).get('authorization') ?? undefined,
316
+ )
317
+ ) {
318
+ return attempt(true);
319
+ }
320
+
279
321
  const { resourceMetadataUrl, scope } =
280
322
  extractWWWAuthenticateParams(response);
281
323
  this.resourceMetadataUrl = resourceMetadataUrl;
282
- const result = await auth(this.authProvider, {
283
- serverUrl: this.url,
284
- resourceMetadataUrl: this.resourceMetadataUrl,
324
+ const result = await this.authorizeOnce(
325
+ this.resourceMetadataUrl,
285
326
  scope,
286
- fetchFn: this.fetchFn,
287
- });
327
+ );
288
328
  if (result !== 'AUTHORIZED') {
289
329
  throw new UnauthorizedError();
290
330
  }
package/src/tool/oauth.ts CHANGED
@@ -1199,6 +1199,10 @@ async function authInternal(
1199
1199
  ): Promise<AuthResult> {
1200
1200
  let resourceMetadata: OAuthProtectedResourceMetadata | undefined;
1201
1201
  let authorizationServerUrl: string | URL | undefined;
1202
+ let clientInformation: OAuthClientInformation | undefined;
1203
+ let callbackAuthorizationServerInformation:
1204
+ | OAuthAuthorizationServerInformation
1205
+ | undefined;
1202
1206
 
1203
1207
  /** Reject Protected Resource Metadata URLs outside the configured MCP server origin. */
1204
1208
  assertResourceMetadataUrlSameOrigin(serverUrl, resourceMetadataUrl);
@@ -1218,9 +1222,27 @@ async function authInternal(
1218
1222
  }
1219
1223
  } catch {}
1220
1224
 
1221
- /** Fall back to legacy MCP behavior where the MCP server is the Authorization Server */
1225
+ /**
1226
+ * A callback may not have the original PRM URL from the authentication
1227
+ * challenge. Use the authorization server pinned before redirecting when
1228
+ * rediscovery does not select one.
1229
+ */
1230
+ if (authorizationCode !== undefined) {
1231
+ clientInformation = await Promise.resolve(provider.clientInformation());
1232
+ if (clientInformation) {
1233
+ callbackAuthorizationServerInformation =
1234
+ await getStoredAuthorizationServerInformation({
1235
+ provider,
1236
+ clientInformation,
1237
+ });
1238
+ }
1239
+ }
1240
+
1241
+ /** Reuse the callback pin, then fall back to the legacy MCP-as-AS behavior. */
1222
1242
  if (!authorizationServerUrl) {
1223
- authorizationServerUrl = serverUrl;
1243
+ authorizationServerUrl =
1244
+ callbackAuthorizationServerInformation?.authorizationServerUrl ??
1245
+ serverUrl;
1224
1246
  }
1225
1247
 
1226
1248
  /** Validate and select the resource value sent to the AS */
@@ -1253,7 +1275,9 @@ async function authInternal(
1253
1275
  });
1254
1276
 
1255
1277
  /** Load or register client credentials with the AS pin attached. */
1256
- let clientInformation = await Promise.resolve(provider.clientInformation());
1278
+ if (authorizationCode === undefined) {
1279
+ clientInformation = await Promise.resolve(provider.clientInformation());
1280
+ }
1257
1281
  if (!clientInformation) {
1258
1282
  if (authorizationCode !== undefined) {
1259
1283
  throw new Error(
@@ -1295,10 +1319,11 @@ async function authInternal(
1295
1319
  }
1296
1320
 
1297
1321
  const storedAuthorizationServerInformation =
1298
- await getStoredAuthorizationServerInformation({
1322
+ callbackAuthorizationServerInformation ??
1323
+ (await getStoredAuthorizationServerInformation({
1299
1324
  provider,
1300
1325
  clientInformation,
1301
- });
1326
+ }));
1302
1327
  if (!storedAuthorizationServerInformation) {
1303
1328
  throw new MCPClientOAuthError({
1304
1329
  message: