@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/CHANGELOG.md +14 -0
- package/README.md +15 -0
- package/dist/index.d.ts +54 -3
- package/dist/index.js +472 -67
- package/dist/index.js.map +1 -1
- package/dist/mcp-stdio/index.d.ts +19 -1
- package/dist/mcp-stdio/index.js +15 -5
- package/dist/mcp-stdio/index.js.map +1 -1
- package/package.json +1 -1
- package/src/tool/json-rpc-message.ts +1 -1
- package/src/tool/mcp-client.ts +251 -22
- package/src/tool/mcp-http-headers.ts +161 -0
- package/src/tool/mcp-http-transport.ts +119 -16
- package/src/tool/mcp-sse-transport.ts +12 -5
- package/src/tool/mcp-stdio/mcp-stdio-transport.ts +1 -0
- package/src/tool/mcp-transport.ts +25 -1
- package/src/tool/mock-mcp-transport.ts +2 -2
- package/src/tool/oauth-types.ts +9 -0
- package/src/tool/oauth.ts +126 -20
- package/src/tool/types.ts +19 -8
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
|
-
|
|
275
|
-
|
|
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
|
|
306
|
+
return {};
|
|
285
307
|
}
|
|
286
308
|
|
|
287
309
|
const [type, scheme] = header.split(' ');
|
|
288
310
|
if (type.toLowerCase() !== 'bearer' || !scheme) {
|
|
289
|
-
return
|
|
311
|
+
return {};
|
|
290
312
|
}
|
|
291
313
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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(
|
|
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:
|
|
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 = '
|
|
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
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
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
|
*/
|