@ai-sdk/mcp 1.0.89 → 1.0.91

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/dist/index.mjs CHANGED
@@ -529,6 +529,8 @@ function checkResourceAllowed({
529
529
 
530
530
  // src/tool/oauth.ts
531
531
  import {
532
+ fetchWithValidatedEndpoint,
533
+ fetchWithValidatedRedirects,
532
534
  parseJSON as parseJSON2,
533
535
  validateDownloadUrl
534
536
  } from "@ai-sdk/provider-utils";
@@ -545,8 +547,8 @@ function isOAuthLoopbackHost(hostname) {
545
547
  const normalized = hostname.toLowerCase().replace(/\.+$/, "");
546
548
  return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "127.0.0.1" || normalized === "[::1]" || normalized === "::1";
547
549
  }
548
- function assertSafeOAuthEndpoint(endpointUrl) {
549
- if ((endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) {
550
+ function assertSafeOAuthEndpoint(endpointUrl, { allowLoopback = false } = {}) {
551
+ if (allowLoopback && (endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) {
550
552
  return;
551
553
  }
552
554
  try {
@@ -558,6 +560,10 @@ function assertSafeOAuthEndpoint(endpointUrl) {
558
560
  });
559
561
  }
560
562
  }
563
+ function getTrustedLoopbackOrigin(authorizationServerUrl) {
564
+ const url = new URL(authorizationServerUrl);
565
+ return isOAuthLoopbackHost(url.hostname) ? url.origin : void 0;
566
+ }
561
567
  function createAuthorizationServerInformation(authorizationServerUrl, metadata) {
562
568
  return {
563
569
  authorizationServerUrl: normalizeUrl(authorizationServerUrl),
@@ -697,13 +703,27 @@ function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
697
703
  }
698
704
  return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
699
705
  }
700
- async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
706
+ async function fetchWithCorsRetry(url, headers, fetchFn = fetch, trustedOrigin) {
701
707
  try {
702
- return await fetchFn(url, { headers });
708
+ return await fetchWithValidatedRedirects({
709
+ url: url.href,
710
+ fetch: async (input, init) => fetchWithValidatedEndpoint({
711
+ url: input,
712
+ init: {
713
+ ...init,
714
+ headers: new Headers(init == null ? void 0 : init.headers).has("MCP-Protocol-Version") ? headers : void 0
715
+ },
716
+ fetch: fetchFn,
717
+ trustedOrigin,
718
+ redirect: "manual"
719
+ }),
720
+ headers,
721
+ trustedOrigin
722
+ });
703
723
  } catch (error) {
704
724
  if (error instanceof TypeError) {
705
725
  if (headers) {
706
- return fetchWithCorsRetry(url, void 0, fetchFn);
726
+ return fetchWithCorsRetry(url, void 0, fetchFn, trustedOrigin);
707
727
  } else {
708
728
  return void 0;
709
729
  }
@@ -711,11 +731,11 @@ async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
711
731
  throw error;
712
732
  }
713
733
  }
714
- async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
734
+ async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch, trustedOrigin) {
715
735
  const headers = {
716
736
  "MCP-Protocol-Version": protocolVersion
717
737
  };
718
- return await fetchWithCorsRetry(url, headers, fetchFn);
738
+ return await fetchWithCorsRetry(url, headers, fetchFn, trustedOrigin);
719
739
  }
720
740
  function shouldAttemptFallback(response, pathname) {
721
741
  return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
@@ -732,10 +752,20 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o
732
752
  url = new URL(wellKnownPath, (_b3 = opts == null ? void 0 : opts.metadataServerUrl) != null ? _b3 : issuer);
733
753
  url.search = issuer.search;
734
754
  }
735
- let response = await tryMetadataDiscovery(url, protocolVersion, fetchFn);
755
+ let response = await tryMetadataDiscovery(
756
+ url,
757
+ protocolVersion,
758
+ fetchFn,
759
+ opts == null ? void 0 : opts.trustedOrigin
760
+ );
736
761
  if (!(opts == null ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
737
762
  const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
738
- response = await tryMetadataDiscovery(rootUrl, protocolVersion, fetchFn);
763
+ response = await tryMetadataDiscovery(
764
+ rootUrl,
765
+ protocolVersion,
766
+ fetchFn,
767
+ opts == null ? void 0 : opts.trustedOrigin
768
+ );
739
769
  }
740
770
  return response;
741
771
  }
@@ -746,7 +776,10 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
746
776
  fetchFn,
747
777
  {
748
778
  protocolVersion: opts == null ? void 0 : opts.protocolVersion,
749
- metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl
779
+ metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl,
780
+ // The configured MCP server is trusted as a request target. Redirects
781
+ // crossing its origin are still validated before they are followed.
782
+ trustedOrigin: new URL(serverUrl).origin
750
783
  }
751
784
  );
752
785
  if (!response || response.status === 404) {
@@ -819,13 +852,19 @@ function assertMetadataIssuerMatches(metadata, expectedIssuer) {
819
852
  }
820
853
  async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
821
854
  fetchFn = fetch,
822
- protocolVersion = LATEST_PROTOCOL_VERSION
855
+ protocolVersion = LATEST_PROTOCOL_VERSION,
856
+ trustedOrigin
823
857
  } = {}) {
824
858
  var _a3;
825
859
  const headers = { "MCP-Protocol-Version": protocolVersion };
826
860
  const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
827
861
  for (const { url: endpointUrl, type, expectedIssuer } of urlsToTry) {
828
- const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
862
+ const response = await fetchWithCorsRetry(
863
+ endpointUrl,
864
+ headers,
865
+ fetchFn,
866
+ trustedOrigin
867
+ );
829
868
  if (!response) {
830
869
  continue;
831
870
  }
@@ -991,7 +1030,10 @@ async function exchangeAuthorization(authorizationServerUrl, {
991
1030
  var _a3;
992
1031
  const grantType = "authorization_code";
993
1032
  const tokenUrl = (metadata == null ? void 0 : metadata.token_endpoint) ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
994
- assertSafeOAuthEndpoint(tokenUrl);
1033
+ const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
1034
+ assertSafeOAuthEndpoint(tokenUrl, {
1035
+ allowLoopback: tokenUrl.origin === trustedOrigin
1036
+ });
995
1037
  if ((metadata == null ? void 0 : metadata.grant_types_supported) && !metadata.grant_types_supported.includes(grantType)) {
996
1038
  throw new Error(
997
1039
  `Incompatible auth server: does not support grant type ${grantType}`
@@ -1025,11 +1067,15 @@ async function exchangeAuthorization(authorizationServerUrl, {
1025
1067
  if (resource) {
1026
1068
  params.set("resource", resourceUrlStripSlash(resource));
1027
1069
  }
1028
- const response = await (fetchFn != null ? fetchFn : fetch)(tokenUrl, {
1029
- method: "POST",
1030
- headers,
1031
- body: params,
1032
- redirect: "error"
1070
+ const response = await fetchWithValidatedEndpoint({
1071
+ url: tokenUrl,
1072
+ init: {
1073
+ method: "POST",
1074
+ headers,
1075
+ body: params
1076
+ },
1077
+ fetch: fetchFn,
1078
+ trustedOrigin
1033
1079
  });
1034
1080
  if (!response.ok) {
1035
1081
  throw await parseErrorResponse(response);
@@ -1057,7 +1103,10 @@ async function refreshAuthorization(authorizationServerUrl, {
1057
1103
  } else {
1058
1104
  tokenUrl = new URL("/token", authorizationServerUrl);
1059
1105
  }
1060
- assertSafeOAuthEndpoint(tokenUrl);
1106
+ const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
1107
+ assertSafeOAuthEndpoint(tokenUrl, {
1108
+ allowLoopback: tokenUrl.origin === trustedOrigin
1109
+ });
1061
1110
  const headers = new Headers({
1062
1111
  "Content-Type": "application/x-www-form-urlencoded",
1063
1112
  Accept: "application/json"
@@ -1084,11 +1133,15 @@ async function refreshAuthorization(authorizationServerUrl, {
1084
1133
  if (resource) {
1085
1134
  params.set("resource", resourceUrlStripSlash(resource));
1086
1135
  }
1087
- const response = await (fetchFn != null ? fetchFn : fetch)(tokenUrl, {
1088
- method: "POST",
1089
- headers,
1090
- body: params,
1091
- redirect: "error"
1136
+ const response = await fetchWithValidatedEndpoint({
1137
+ url: tokenUrl,
1138
+ init: {
1139
+ method: "POST",
1140
+ headers,
1141
+ body: params
1142
+ },
1143
+ fetch: fetchFn,
1144
+ trustedOrigin
1092
1145
  });
1093
1146
  if (!response.ok) {
1094
1147
  throw await parseErrorResponse(response);
@@ -1114,14 +1167,21 @@ async function registerClient(authorizationServerUrl, {
1114
1167
  } else {
1115
1168
  registrationUrl = new URL("/register", authorizationServerUrl);
1116
1169
  }
1117
- assertSafeOAuthEndpoint(registrationUrl);
1118
- const response = await (fetchFn != null ? fetchFn : fetch)(registrationUrl, {
1119
- method: "POST",
1120
- headers: {
1121
- "Content-Type": "application/json"
1170
+ const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
1171
+ assertSafeOAuthEndpoint(registrationUrl, {
1172
+ allowLoopback: registrationUrl.origin === trustedOrigin
1173
+ });
1174
+ const response = await fetchWithValidatedEndpoint({
1175
+ url: registrationUrl,
1176
+ init: {
1177
+ method: "POST",
1178
+ headers: {
1179
+ "Content-Type": "application/json"
1180
+ },
1181
+ body: JSON.stringify(clientMetadata)
1122
1182
  },
1123
- body: JSON.stringify(clientMetadata),
1124
- redirect: "error"
1183
+ fetch: fetchFn,
1184
+ trustedOrigin
1125
1185
  });
1126
1186
  if (!response.ok) {
1127
1187
  throw await parseErrorResponse(response);
@@ -1130,8 +1190,9 @@ async function registerClient(authorizationServerUrl, {
1130
1190
  }
1131
1191
  async function auth(provider, options) {
1132
1192
  var _a3, _b3, _c;
1193
+ const refreshAttempt = {};
1133
1194
  try {
1134
- return await authInternal(provider, options);
1195
+ return await authInternal(provider, options, refreshAttempt);
1135
1196
  } catch (error) {
1136
1197
  if (error instanceof InvalidClientError || error instanceof UnauthorizedClientError) {
1137
1198
  if (options.authorizationCode !== void 0 || !await ((_a3 = provider.isClientInformationDynamicallyRegistered) == null ? void 0 : _a3.call(provider))) {
@@ -1139,8 +1200,10 @@ async function auth(provider, options) {
1139
1200
  }
1140
1201
  await ((_b3 = provider.invalidateCredentials) == null ? void 0 : _b3.call(provider, "all"));
1141
1202
  return await authInternal(provider, options);
1142
- } else if (error instanceof InvalidGrantError) {
1143
- await ((_c = provider.invalidateCredentials) == null ? void 0 : _c.call(provider, "tokens"));
1203
+ } else if (error instanceof InvalidGrantError && refreshAttempt.tokens) {
1204
+ await ((_c = provider.invalidateCredentials) == null ? void 0 : _c.call(provider, "tokens", {
1205
+ tokens: refreshAttempt.tokens
1206
+ }));
1144
1207
  return await authInternal(provider, options);
1145
1208
  }
1146
1209
  throw error;
@@ -1174,7 +1237,7 @@ async function authInternal(provider, {
1174
1237
  scope,
1175
1238
  resourceMetadataUrl,
1176
1239
  fetchFn
1177
- }) {
1240
+ }, refreshAttempt) {
1178
1241
  var _a3, _b3, _c;
1179
1242
  let resourceMetadata;
1180
1243
  let authorizationServerUrl;
@@ -1204,6 +1267,14 @@ async function authInternal(provider, {
1204
1267
  if (!authorizationServerUrl) {
1205
1268
  authorizationServerUrl = (_a3 = callbackAuthorizationServerInformation == null ? void 0 : callbackAuthorizationServerInformation.authorizationServerUrl) != null ? _a3 : serverUrl;
1206
1269
  }
1270
+ const parsedServerUrl = new URL(serverUrl);
1271
+ const parsedAuthorizationServerUrl = new URL(authorizationServerUrl);
1272
+ const serverOrigin = parsedServerUrl.origin;
1273
+ const authorizationServerOrigin = parsedAuthorizationServerUrl.origin;
1274
+ const trustedAuthorizationServerOrigin = authorizationServerOrigin === serverOrigin || isOAuthLoopbackHost(parsedServerUrl.hostname) && isOAuthLoopbackHost(parsedAuthorizationServerUrl.hostname) ? authorizationServerOrigin : void 0;
1275
+ if (!trustedAuthorizationServerOrigin) {
1276
+ assertSafeOAuthEndpoint(new URL(authorizationServerUrl));
1277
+ }
1207
1278
  const resource = await selectResourceURL(
1208
1279
  serverUrl,
1209
1280
  provider,
@@ -1217,7 +1288,8 @@ async function authInternal(provider, {
1217
1288
  const metadata = await discoverAuthorizationServerMetadata(
1218
1289
  authorizationServerUrl,
1219
1290
  {
1220
- fetchFn
1291
+ fetchFn,
1292
+ trustedOrigin: trustedAuthorizationServerOrigin
1221
1293
  }
1222
1294
  );
1223
1295
  const currentAuthorizationServerInformation = createAuthorizationServerInformation(authorizationServerUrl, metadata);
@@ -1309,10 +1381,15 @@ async function authInternal(provider, {
1309
1381
  currentAuthorizationServerInformation
1310
1382
  });
1311
1383
  } else {
1312
- await ((_c = provider.invalidateCredentials) == null ? void 0 : _c.call(provider, "tokens"));
1384
+ await ((_c = provider.invalidateCredentials) == null ? void 0 : _c.call(provider, "tokens", {
1385
+ tokens: { ...tokens }
1386
+ }));
1313
1387
  }
1314
1388
  try {
1315
1389
  if (storedAuthorizationServerInformation) {
1390
+ if (refreshAttempt) {
1391
+ refreshAttempt.tokens = { ...tokens };
1392
+ }
1316
1393
  const newTokens = await refreshAuthorization(authorizationServerUrl, {
1317
1394
  metadata,
1318
1395
  clientInformation,