@ai-sdk/mcp 1.0.69 → 1.0.71

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.69",
3
+ "version": "1.0.71",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "pkce-challenge": "^5.0.0",
36
36
  "@ai-sdk/provider": "3.0.15",
37
- "@ai-sdk/provider-utils": "4.0.44"
37
+ "@ai-sdk/provider-utils": "4.0.46"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "20.17.24",
@@ -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,
@@ -113,7 +113,10 @@ export class HttpMCPTransport implements MCPTransport {
113
113
  /**
114
114
  * Runs a single OAuth recovery flow for concurrent 401 responses.
115
115
  */
116
- private authorizeOnce(resourceMetadataUrl?: URL): Promise<AuthResult> {
116
+ private authorizeOnce(
117
+ resourceMetadataUrl?: URL,
118
+ scope?: string,
119
+ ): Promise<AuthResult> {
117
120
  if (!this.authProvider) {
118
121
  return Promise.resolve('REDIRECT');
119
122
  }
@@ -122,6 +125,7 @@ export class HttpMCPTransport implements MCPTransport {
122
125
  this.authPromise = auth(this.authProvider, {
123
126
  serverUrl: this.url,
124
127
  resourceMetadataUrl,
128
+ scope,
125
129
  fetchFn: this.fetchFn,
126
130
  }).finally(() => {
127
131
  this.authPromise = undefined;
@@ -201,9 +205,14 @@ export class HttpMCPTransport implements MCPTransport {
201
205
  }
202
206
 
203
207
  if (response.status === 401 && this.authProvider && !triedAuth) {
204
- this.resourceMetadataUrl = extractResourceMetadataUrl(response);
208
+ const { resourceMetadataUrl, scope } =
209
+ extractWWWAuthenticateParams(response);
210
+ this.resourceMetadataUrl = resourceMetadataUrl;
205
211
  try {
206
- const result = await this.authorizeOnce(this.resourceMetadataUrl);
212
+ const result = await this.authorizeOnce(
213
+ this.resourceMetadataUrl,
214
+ scope,
215
+ );
207
216
  if (result !== 'AUTHORIZED') {
208
217
  const error = new UnauthorizedError();
209
218
  throw error;
@@ -412,9 +421,14 @@ export class HttpMCPTransport implements MCPTransport {
412
421
  }
413
422
 
414
423
  if (response.status === 401 && this.authProvider && !triedAuth) {
415
- this.resourceMetadataUrl = extractResourceMetadataUrl(response);
424
+ const { resourceMetadataUrl, scope } =
425
+ extractWWWAuthenticateParams(response);
426
+ this.resourceMetadataUrl = resourceMetadataUrl;
416
427
  try {
417
- const result = await this.authorizeOnce(this.resourceMetadataUrl);
428
+ const result = await this.authorizeOnce(
429
+ this.resourceMetadataUrl,
430
+ scope,
431
+ );
418
432
  if (result !== 'AUTHORIZED') {
419
433
  const error = new UnauthorizedError();
420
434
  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
@@ -271,37 +271,65 @@ function assertAuthorizationServerInformationMatches({
271
271
  }
272
272
  }
273
273
 
274
- /**
275
- * Extracts the OAuth 2.0 Protected Resource Metadata URL from a WWW-Authenticate header (RFC9728).
276
- * Looks for a resource="..." parameter.
277
- */
278
- export function extractResourceMetadataUrl(
279
- response: Response,
280
- ): URL | undefined {
274
+ export function extractWWWAuthenticateParams(response: Response): {
275
+ resourceMetadataUrl?: URL;
276
+ scope?: string;
277
+ } {
281
278
  const header =
282
279
  response.headers.get('www-authenticate') ??
283
280
  response.headers.get('WWW-Authenticate');
284
281
  if (!header) {
285
- return undefined;
282
+ return {};
286
283
  }
287
284
 
288
285
  const [type, scheme] = header.split(' ');
289
- if (type.toLowerCase() !== 'bearer' || !scheme) {
290
- return undefined;
286
+ if (type.replace(/,$/, '').toLowerCase() !== 'bearer' || !scheme) {
287
+ return {};
291
288
  }
292
289
 
293
- // regex taken from MCP spec
294
- const regex = /resource_metadata="([^"]*)"/;
295
- const match = header.match(regex);
296
- if (!match) {
297
- return undefined;
298
- }
290
+ const resourceMetadataMatch = header.match(
291
+ /(?:^|[,\s])resource_metadata="([^"]*)"/i,
292
+ );
293
+ const scope = header.match(/(?:^|[,\s])scope="([^"]*)"/i)?.[1];
299
294
 
295
+ let resourceMetadataUrl: URL | undefined;
300
296
  try {
301
- return new URL(match[1]);
302
- } catch {
303
- return undefined;
297
+ resourceMetadataUrl = resourceMetadataMatch
298
+ ? new URL(resourceMetadataMatch[1])
299
+ : undefined;
300
+ } catch {}
301
+
302
+ return { resourceMetadataUrl, scope };
303
+ }
304
+
305
+ /**
306
+ * Extracts the OAuth 2.0 Protected Resource Metadata URL from a WWW-Authenticate header (RFC9728).
307
+ */
308
+ export function extractResourceMetadataUrl(
309
+ response: Response,
310
+ ): URL | undefined {
311
+ return extractWWWAuthenticateParams(response).resourceMetadataUrl;
312
+ }
313
+
314
+ function selectScope({
315
+ scope,
316
+ resourceMetadata,
317
+ clientMetadata,
318
+ }: {
319
+ scope?: string;
320
+ resourceMetadata?: OAuthProtectedResourceMetadata;
321
+ clientMetadata: OAuthClientMetadata;
322
+ }): string | undefined {
323
+ if (scope) {
324
+ return scope;
304
325
  }
326
+
327
+ const resourceScopes = resourceMetadata?.scopes_supported?.join(' ');
328
+ if (resourceScopes) {
329
+ return resourceScopes;
330
+ }
331
+
332
+ return clientMetadata.scope;
305
333
  }
306
334
 
307
335
  /**
@@ -1309,7 +1337,11 @@ async function authInternal(
1309
1337
  clientInformation,
1310
1338
  state,
1311
1339
  redirectUrl: provider.redirectUrl,
1312
- scope: scope || provider.clientMetadata.scope,
1340
+ scope: selectScope({
1341
+ scope,
1342
+ resourceMetadata,
1343
+ clientMetadata: provider.clientMetadata,
1344
+ }),
1313
1345
  resource,
1314
1346
  },
1315
1347
  );