@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/CHANGELOG.md +19 -0
- package/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +112 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +114 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/tool/oauth.ts +145 -36
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
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 (
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
|
|
1124
|
-
|
|
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,
|