@ai-sdk/mcp 2.0.30 → 2.0.32

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": "2.0.30",
3
+ "version": "2.0.32",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -32,8 +32,8 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "pkce-challenge": "^5.0.1",
35
- "@ai-sdk/provider": "4.0.7",
36
- "@ai-sdk/provider-utils": "5.0.26"
35
+ "@ai-sdk/provider-utils": "5.0.27",
36
+ "@ai-sdk/provider": "4.0.7"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "22.19.19",
@@ -13,7 +13,7 @@ import {
13
13
  import type { MCPTransport } from './mcp-transport';
14
14
  import { VERSION } from '../version';
15
15
  import {
16
- extractResourceMetadataUrl,
16
+ extractWWWAuthenticateParams,
17
17
  UnauthorizedError,
18
18
  auth,
19
19
  type AuthResult,
@@ -159,7 +159,10 @@ export class HttpMCPTransport implements MCPTransport {
159
159
  /**
160
160
  * Runs a single OAuth recovery flow for concurrent 401 responses.
161
161
  */
162
- private authorizeOnce(resourceMetadataUrl?: URL): Promise<AuthResult> {
162
+ private authorizeOnce(
163
+ resourceMetadataUrl?: URL,
164
+ scope?: string,
165
+ ): Promise<AuthResult> {
163
166
  if (!this.authProvider) {
164
167
  return Promise.resolve('REDIRECT');
165
168
  }
@@ -168,6 +171,7 @@ export class HttpMCPTransport implements MCPTransport {
168
171
  this.authPromise = auth(this.authProvider, {
169
172
  serverUrl: this.url,
170
173
  resourceMetadataUrl,
174
+ scope,
171
175
  fetchFn: this.fetchFn,
172
176
  }).finally(() => {
173
177
  this.authPromise = undefined;
@@ -256,9 +260,14 @@ export class HttpMCPTransport implements MCPTransport {
256
260
  this.applySessionIdFromResponse(response);
257
261
 
258
262
  if (response.status === 401 && this.authProvider && !triedAuth) {
259
- this.resourceMetadataUrl = extractResourceMetadataUrl(response);
263
+ const { resourceMetadataUrl, scope } =
264
+ extractWWWAuthenticateParams(response);
265
+ this.resourceMetadataUrl = resourceMetadataUrl;
260
266
  try {
261
- const result = await this.authorizeOnce(this.resourceMetadataUrl);
267
+ const result = await this.authorizeOnce(
268
+ this.resourceMetadataUrl,
269
+ scope,
270
+ );
262
271
  if (result !== 'AUTHORIZED') {
263
272
  const error = new UnauthorizedError();
264
273
  throw error;
@@ -473,9 +482,14 @@ export class HttpMCPTransport implements MCPTransport {
473
482
  this.applySessionIdFromResponse(response);
474
483
 
475
484
  if (response.status === 401 && this.authProvider && !triedAuth) {
476
- this.resourceMetadataUrl = extractResourceMetadataUrl(response);
485
+ const { resourceMetadataUrl, scope } =
486
+ extractWWWAuthenticateParams(response);
487
+ this.resourceMetadataUrl = resourceMetadataUrl;
477
488
  try {
478
- const result = await this.authorizeOnce(this.resourceMetadataUrl);
489
+ const result = await this.authorizeOnce(
490
+ this.resourceMetadataUrl,
491
+ scope,
492
+ );
479
493
  if (result !== 'AUTHORIZED') {
480
494
  const error = new UnauthorizedError();
481
495
  this.onerror?.(error);
@@ -9,7 +9,7 @@ import { parseJSONRPCMessage, type JSONRPCMessage } from './json-rpc-message';
9
9
  import type { MCPTransport } from './mcp-transport';
10
10
  import { VERSION } from '../version';
11
11
  import {
12
- extractResourceMetadataUrl,
12
+ extractWWWAuthenticateParams,
13
13
  UnauthorizedError,
14
14
  auth,
15
15
  type OAuthClientProvider,
@@ -106,11 +106,14 @@ export class SseMCPTransport implements MCPTransport {
106
106
  });
107
107
 
108
108
  if (response.status === 401 && this.authProvider && !triedAuth) {
109
- this.resourceMetadataUrl = extractResourceMetadataUrl(response);
109
+ const { resourceMetadataUrl, scope } =
110
+ extractWWWAuthenticateParams(response);
111
+ this.resourceMetadataUrl = resourceMetadataUrl;
110
112
  try {
111
113
  const result = await auth(this.authProvider, {
112
114
  serverUrl: this.url,
113
115
  resourceMetadataUrl: this.resourceMetadataUrl,
116
+ scope,
114
117
  fetchFn: this.fetchFn,
115
118
  });
116
119
  if (result !== 'AUTHORIZED') {
@@ -273,11 +276,14 @@ export class SseMCPTransport implements MCPTransport {
273
276
  const response = await this.fetchFn(endpoint.href, init);
274
277
 
275
278
  if (response.status === 401 && this.authProvider && !triedAuth) {
276
- this.resourceMetadataUrl = extractResourceMetadataUrl(response);
279
+ const { resourceMetadataUrl, scope } =
280
+ extractWWWAuthenticateParams(response);
281
+ this.resourceMetadataUrl = resourceMetadataUrl;
277
282
  try {
278
283
  const result = await auth(this.authProvider, {
279
284
  serverUrl: this.url,
280
285
  resourceMetadataUrl: this.resourceMetadataUrl,
286
+ scope,
281
287
  fetchFn: this.fetchFn,
282
288
  });
283
289
  if (result !== 'AUTHORIZED') {
package/src/tool/oauth.ts CHANGED
@@ -270,37 +270,65 @@ function assertAuthorizationServerInformationMatches({
270
270
  }
271
271
  }
272
272
 
273
- /**
274
- * Extracts the OAuth 2.0 Protected Resource Metadata URL from a WWW-Authenticate header (RFC9728).
275
- * Looks for a resource="..." parameter.
276
- */
277
- export function extractResourceMetadataUrl(
278
- response: Response,
279
- ): URL | undefined {
273
+ export function extractWWWAuthenticateParams(response: Response): {
274
+ resourceMetadataUrl?: URL;
275
+ scope?: string;
276
+ } {
280
277
  const header =
281
278
  response.headers.get('www-authenticate') ??
282
279
  response.headers.get('WWW-Authenticate');
283
280
  if (!header) {
284
- return undefined;
281
+ return {};
285
282
  }
286
283
 
287
284
  const [type, scheme] = header.split(' ');
288
285
  if (type.toLowerCase() !== 'bearer' || !scheme) {
289
- return undefined;
286
+ return {};
290
287
  }
291
288
 
292
- // regex taken from MCP spec
293
- const regex = /resource_metadata="([^"]*)"/;
294
- const match = header.match(regex);
295
- if (!match) {
296
- return undefined;
297
- }
289
+ const resourceMetadataMatch = header.match(
290
+ /(?:^|[,\s])resource_metadata="([^"]*)"/i,
291
+ );
292
+ const scope = header.match(/(?:^|[,\s])scope="([^"]*)"/i)?.[1];
298
293
 
294
+ let resourceMetadataUrl: URL | undefined;
299
295
  try {
300
- return new URL(match[1]);
301
- } catch {
302
- return undefined;
296
+ resourceMetadataUrl = resourceMetadataMatch
297
+ ? new URL(resourceMetadataMatch[1])
298
+ : undefined;
299
+ } catch {}
300
+
301
+ return { resourceMetadataUrl, scope };
302
+ }
303
+
304
+ /**
305
+ * Extracts the OAuth 2.0 Protected Resource Metadata URL from a WWW-Authenticate header (RFC9728).
306
+ */
307
+ export function extractResourceMetadataUrl(
308
+ response: Response,
309
+ ): URL | undefined {
310
+ return extractWWWAuthenticateParams(response).resourceMetadataUrl;
311
+ }
312
+
313
+ function selectScope({
314
+ scope,
315
+ resourceMetadata,
316
+ clientMetadata,
317
+ }: {
318
+ scope?: string;
319
+ resourceMetadata?: OAuthProtectedResourceMetadata;
320
+ clientMetadata: OAuthClientMetadata;
321
+ }): string | undefined {
322
+ if (scope) {
323
+ return scope;
303
324
  }
325
+
326
+ const resourceScopes = resourceMetadata?.scopes_supported?.join(' ');
327
+ if (resourceScopes) {
328
+ return resourceScopes;
329
+ }
330
+
331
+ return clientMetadata.scope;
304
332
  }
305
333
 
306
334
  /**
@@ -1308,7 +1336,11 @@ async function authInternal(
1308
1336
  clientInformation,
1309
1337
  state,
1310
1338
  redirectUrl: provider.redirectUrl,
1311
- scope: scope || provider.clientMetadata.scope,
1339
+ scope: selectScope({
1340
+ scope,
1341
+ resourceMetadata,
1342
+ clientMetadata: provider.clientMetadata,
1343
+ }),
1312
1344
  resource,
1313
1345
  },
1314
1346
  );