@ai-sdk/mcp 2.0.31 → 2.0.33

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/src/tool/oauth.ts CHANGED
@@ -31,6 +31,7 @@ import { parseJSON, type FetchFunction } from '@ai-sdk/provider-utils';
31
31
  export type AuthResult = 'AUTHORIZED' | 'REDIRECT';
32
32
 
33
33
  export interface OAuthAuthorizationServerInformation {
34
+ issuer?: string;
34
35
  authorizationServerUrl: string;
35
36
  tokenEndpoint: string;
36
37
  }
@@ -122,11 +123,26 @@ function normalizeUrl(url: string | URL): string {
122
123
  return new URL(url).href;
123
124
  }
124
125
 
126
+ function validateAuthorizationResponseIssuer({
127
+ callbackIssuer,
128
+ expectedIssuer,
129
+ }: {
130
+ callbackIssuer: string | undefined;
131
+ expectedIssuer: string;
132
+ }): void {
133
+ if (callbackIssuer != null && callbackIssuer !== expectedIssuer) {
134
+ throw new MCPClientOAuthError({
135
+ message: `OAuth authorization response issuer ${callbackIssuer} does not match expected issuer ${expectedIssuer}`,
136
+ });
137
+ }
138
+ }
139
+
125
140
  function createAuthorizationServerInformation(
126
141
  authorizationServerUrl: string | URL,
127
142
  metadata?: AuthorizationServerMetadata,
128
143
  ): OAuthAuthorizationServerInformation {
129
144
  return {
145
+ issuer: metadata?.issuer ?? String(authorizationServerUrl),
130
146
  authorizationServerUrl: normalizeUrl(authorizationServerUrl),
131
147
  tokenEndpoint: normalizeUrl(
132
148
  metadata?.token_endpoint
@@ -142,6 +158,7 @@ function addAuthorizationServerInformationToTokens(
142
158
  ): OAuthTokens {
143
159
  return {
144
160
  ...tokens,
161
+ issuer: authorizationServerInformation.issuer,
145
162
  authorization_server: authorizationServerInformation.authorizationServerUrl,
146
163
  token_endpoint: authorizationServerInformation.tokenEndpoint,
147
164
  };
@@ -155,12 +172,14 @@ function addAuthorizationServerInformationToClientInformation<
155
172
  ): CLIENT_INFORMATION {
156
173
  return {
157
174
  ...clientInformation,
175
+ issuer: authorizationServerInformation.issuer,
158
176
  authorization_server: authorizationServerInformation.authorizationServerUrl,
159
177
  token_endpoint: authorizationServerInformation.tokenEndpoint,
160
178
  };
161
179
  }
162
180
 
163
181
  function getAuthorizationServerInformationFromCredentials(credentials?: {
182
+ issuer?: string;
164
183
  authorization_server?: string;
165
184
  token_endpoint?: string;
166
185
  }): OAuthAuthorizationServerInformation | undefined {
@@ -169,6 +188,7 @@ function getAuthorizationServerInformationFromCredentials(credentials?: {
169
188
  }
170
189
 
171
190
  return {
191
+ issuer: credentials.issuer,
172
192
  authorizationServerUrl: normalizeUrl(credentials.authorization_server),
173
193
  tokenEndpoint: normalizeUrl(credentials.token_endpoint),
174
194
  };
@@ -193,6 +213,7 @@ async function getStoredAuthorizationServerInformation({
193
213
  await provider.authorizationServerInformation?.();
194
214
  if (providerAuthorizationServerInformation) {
195
215
  return {
216
+ issuer: providerAuthorizationServerInformation.issuer,
196
217
  authorizationServerUrl: normalizeUrl(
197
218
  providerAuthorizationServerInformation.authorizationServerUrl,
198
219
  ),
@@ -258,6 +279,10 @@ function assertAuthorizationServerInformationMatches({
258
279
  currentAuthorizationServerInformation: OAuthAuthorizationServerInformation;
259
280
  }): void {
260
281
  if (
282
+ (storedAuthorizationServerInformation.issuer != null &&
283
+ currentAuthorizationServerInformation.issuer != null &&
284
+ storedAuthorizationServerInformation.issuer !==
285
+ currentAuthorizationServerInformation.issuer) ||
261
286
  storedAuthorizationServerInformation.authorizationServerUrl !==
262
287
  currentAuthorizationServerInformation.authorizationServerUrl ||
263
288
  storedAuthorizationServerInformation.tokenEndpoint !==
@@ -270,37 +295,65 @@ function assertAuthorizationServerInformationMatches({
270
295
  }
271
296
  }
272
297
 
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 {
298
+ export function extractWWWAuthenticateParams(response: Response): {
299
+ resourceMetadataUrl?: URL;
300
+ scope?: string;
301
+ } {
280
302
  const header =
281
303
  response.headers.get('www-authenticate') ??
282
304
  response.headers.get('WWW-Authenticate');
283
305
  if (!header) {
284
- return undefined;
306
+ return {};
285
307
  }
286
308
 
287
309
  const [type, scheme] = header.split(' ');
288
310
  if (type.toLowerCase() !== 'bearer' || !scheme) {
289
- return undefined;
311
+ return {};
290
312
  }
291
313
 
292
- // regex taken from MCP spec
293
- const regex = /resource_metadata="([^"]*)"/;
294
- const match = header.match(regex);
295
- if (!match) {
296
- return undefined;
297
- }
314
+ const resourceMetadataMatch = header.match(
315
+ /(?:^|[,\s])resource_metadata="([^"]*)"/i,
316
+ );
317
+ const scope = header.match(/(?:^|[,\s])scope="([^"]*)"/i)?.[1];
298
318
 
319
+ let resourceMetadataUrl: URL | undefined;
299
320
  try {
300
- return new URL(match[1]);
301
- } catch {
302
- return undefined;
321
+ resourceMetadataUrl = resourceMetadataMatch
322
+ ? new URL(resourceMetadataMatch[1])
323
+ : undefined;
324
+ } catch {}
325
+
326
+ return { resourceMetadataUrl, scope };
327
+ }
328
+
329
+ /**
330
+ * Extracts the OAuth 2.0 Protected Resource Metadata URL from a WWW-Authenticate header (RFC9728).
331
+ */
332
+ export function extractResourceMetadataUrl(
333
+ response: Response,
334
+ ): URL | undefined {
335
+ return extractWWWAuthenticateParams(response).resourceMetadataUrl;
336
+ }
337
+
338
+ function selectScope({
339
+ scope,
340
+ resourceMetadata,
341
+ clientMetadata,
342
+ }: {
343
+ scope?: string;
344
+ resourceMetadata?: OAuthProtectedResourceMetadata;
345
+ clientMetadata: OAuthClientMetadata;
346
+ }): string | undefined {
347
+ if (scope) {
348
+ return scope;
349
+ }
350
+
351
+ const resourceScopes = resourceMetadata?.scopes_supported?.join(' ');
352
+ if (resourceScopes) {
353
+ return resourceScopes;
303
354
  }
355
+
356
+ return clientMetadata.scope;
304
357
  }
305
358
 
306
359
  /**
@@ -1022,12 +1075,18 @@ export async function registerClient(
1022
1075
  registrationUrl = new URL('/register', authorizationServerUrl);
1023
1076
  }
1024
1077
 
1078
+ const applicationType =
1079
+ clientMetadata.application_type ??
1080
+ inferOAuthApplicationType(clientMetadata.redirect_uris);
1025
1081
  const response = await (fetchFn ?? fetch)(registrationUrl, {
1026
1082
  method: 'POST',
1027
1083
  headers: {
1028
1084
  'Content-Type': 'application/json',
1029
1085
  },
1030
- body: JSON.stringify(clientMetadata),
1086
+ body: JSON.stringify({
1087
+ ...clientMetadata,
1088
+ application_type: applicationType,
1089
+ }),
1031
1090
  });
1032
1091
 
1033
1092
  if (!response.ok) {
@@ -1037,12 +1096,32 @@ export async function registerClient(
1037
1096
  return OAuthClientInformationFullSchema.parse(await response.json());
1038
1097
  }
1039
1098
 
1099
+ function inferOAuthApplicationType(redirectUris: string[]): 'native' | 'web' {
1100
+ const isNativeRedirectUri = (redirectUri: string): boolean => {
1101
+ const url = new URL(redirectUri);
1102
+ return (
1103
+ ((url.protocol === 'http:' || url.protocol === 'https:') &&
1104
+ (url.hostname === 'localhost' ||
1105
+ url.hostname.endsWith('.localhost') ||
1106
+ url.hostname === '127.0.0.1' ||
1107
+ url.hostname === '[::1]')) ||
1108
+ (url.protocol !== 'http:' && url.protocol !== 'https:')
1109
+ );
1110
+ };
1111
+
1112
+ return redirectUris.every(isNativeRedirectUri) ? 'native' : 'web';
1113
+ }
1114
+
1040
1115
  export async function auth(
1041
1116
  provider: OAuthClientProvider,
1042
1117
  options: {
1043
1118
  serverUrl: string | URL;
1044
1119
  authorizationCode?: string;
1045
1120
  callbackState?: string;
1121
+ /**
1122
+ * Value of the `iss` parameter from the authorization response.
1123
+ */
1124
+ callbackIssuer?: string;
1046
1125
  scope?: string;
1047
1126
  resourceMetadataUrl?: URL;
1048
1127
  fetchFn?: FetchFunction;
@@ -1103,6 +1182,7 @@ async function authInternal(
1103
1182
  serverUrl,
1104
1183
  authorizationCode,
1105
1184
  callbackState,
1185
+ callbackIssuer,
1106
1186
  scope,
1107
1187
  resourceMetadataUrl,
1108
1188
  fetchFn,
@@ -1110,6 +1190,7 @@ async function authInternal(
1110
1190
  serverUrl: string | URL;
1111
1191
  authorizationCode?: string;
1112
1192
  callbackState?: string;
1193
+ callbackIssuer?: string;
1113
1194
  scope?: string;
1114
1195
  resourceMetadataUrl?: URL;
1115
1196
  fetchFn?: FetchFunction;
@@ -1166,6 +1247,20 @@ async function authInternal(
1166
1247
 
1167
1248
  /** Load or register client credentials with the AS pin attached. */
1168
1249
  let clientInformation = await Promise.resolve(provider.clientInformation());
1250
+ if (clientInformation?.issuer != null) {
1251
+ const storedAuthorizationServerInformation =
1252
+ await getStoredAuthorizationServerInformation({
1253
+ provider,
1254
+ clientInformation,
1255
+ });
1256
+ if (storedAuthorizationServerInformation) {
1257
+ assertAuthorizationServerInformationMatches({
1258
+ storedAuthorizationServerInformation,
1259
+ currentAuthorizationServerInformation,
1260
+ });
1261
+ }
1262
+ }
1263
+
1169
1264
  if (!clientInformation) {
1170
1265
  if (authorizationCode !== undefined) {
1171
1266
  throw new Error(
@@ -1214,6 +1309,13 @@ async function authInternal(
1214
1309
  'Stored OAuth authorization server metadata is required when exchanging an authorization code',
1215
1310
  });
1216
1311
  }
1312
+ validateAuthorizationResponseIssuer({
1313
+ callbackIssuer,
1314
+ expectedIssuer:
1315
+ storedAuthorizationServerInformation.issuer ??
1316
+ metadata?.issuer ??
1317
+ String(authorizationServerUrl),
1318
+ });
1217
1319
  assertAuthorizationServerInformationMatches({
1218
1320
  storedAuthorizationServerInformation,
1219
1321
  currentAuthorizationServerInformation,
@@ -1308,7 +1410,11 @@ async function authInternal(
1308
1410
  clientInformation,
1309
1411
  state,
1310
1412
  redirectUrl: provider.redirectUrl,
1311
- scope: scope || provider.clientMetadata.scope,
1413
+ scope: selectScope({
1414
+ scope,
1415
+ resourceMetadata,
1416
+ clientMetadata: provider.clientMetadata,
1417
+ }),
1312
1418
  resource,
1313
1419
  },
1314
1420
  );
package/src/tool/types.ts CHANGED
@@ -2,9 +2,11 @@ import { z } from 'zod/v4';
2
2
  import type { JSONObject } from '@ai-sdk/provider';
3
3
  import type { FlexibleSchema, Tool } from '@ai-sdk/provider-utils';
4
4
 
5
- export const LATEST_PROTOCOL_VERSION = '2025-11-25';
5
+ export const LATEST_PROTOCOL_VERSION = '2026-07-28';
6
+ export const LATEST_LEGACY_PROTOCOL_VERSION = '2025-11-25';
6
7
  export const SUPPORTED_PROTOCOL_VERSIONS = [
7
8
  LATEST_PROTOCOL_VERSION,
9
+ LATEST_LEGACY_PROTOCOL_VERSION,
8
10
  '2025-06-18',
9
11
  '2025-03-26',
10
12
  '2024-11-05',
@@ -73,7 +75,9 @@ export const BaseParamsSchema = z.looseObject({
73
75
  _meta: z.optional(z.object({}).loose()),
74
76
  });
75
77
  type BaseParams = z.infer<typeof BaseParamsSchema>;
76
- export const ResultSchema = BaseParamsSchema;
78
+ export const ResultSchema = BaseParamsSchema.extend({
79
+ resultType: z.optional(z.string()),
80
+ });
77
81
 
78
82
  export const RequestSchema = z.object({
79
83
  method: z.string(),
@@ -128,6 +132,15 @@ export const ClientCapabilitiesSchema = z
128
132
  export type ClientCapabilities = z.infer<typeof ClientCapabilitiesSchema>;
129
133
  export type ElicitationCapability = z.infer<typeof ElicitationCapabilitySchema>;
130
134
 
135
+ export const DiscoverResultSchema = ResultSchema.extend({
136
+ supportedVersions: z.array(z.string()),
137
+ capabilities: ServerCapabilitiesSchema,
138
+ instructions: z.optional(z.string()),
139
+ ttlMs: z.optional(z.number()),
140
+ cacheScope: z.optional(z.union([z.literal('public'), z.literal('private')])),
141
+ });
142
+ export type DiscoverResult = z.infer<typeof DiscoverResultSchema>;
143
+
131
144
  export const InitializeResultSchema = ResultSchema.extend({
132
145
  protocolVersion: z.string(),
133
146
  capabilities: ServerCapabilitiesSchema,
@@ -154,12 +167,10 @@ const ToolSchema = z
154
167
  */
155
168
  title: z.optional(z.string()),
156
169
  description: z.optional(z.string()),
157
- inputSchema: z
158
- .object({
159
- type: z.literal('object'),
160
- properties: z.optional(z.object({}).loose()),
161
- })
162
- .loose(),
170
+ inputSchema: z.looseObject({
171
+ type: z.optional(z.unknown()),
172
+ properties: z.optional(z.object({}).loose()),
173
+ }),
163
174
  /**
164
175
  * @see https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
165
176
  */