@ai-sdk/mcp 1.0.89 → 1.0.90
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 +11 -0
- package/dist/index.js +99 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +101 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/tool/oauth.ts +125 -32
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @ai-sdk/mcp
|
|
2
2
|
|
|
3
|
+
## 1.0.90
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a9cea74: Keep default Node.js downloads protected by DNS validation and connection pinning when frameworks or instrumentation wrap global fetch before or after the SDK loads.
|
|
8
|
+
- a9cea74: fix(mcp): prevent SSRF in OAuth metadata discovery
|
|
9
|
+
- Updated dependencies [a9cea74]
|
|
10
|
+
- Updated dependencies [a9cea74]
|
|
11
|
+
- Updated dependencies [a9cea74]
|
|
12
|
+
- @ai-sdk/provider-utils@4.0.57
|
|
13
|
+
|
|
3
14
|
## 1.0.89
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -572,8 +572,8 @@ function isOAuthLoopbackHost(hostname) {
|
|
|
572
572
|
const normalized = hostname.toLowerCase().replace(/\.+$/, "");
|
|
573
573
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "127.0.0.1" || normalized === "[::1]" || normalized === "::1";
|
|
574
574
|
}
|
|
575
|
-
function assertSafeOAuthEndpoint(endpointUrl) {
|
|
576
|
-
if ((endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) {
|
|
575
|
+
function assertSafeOAuthEndpoint(endpointUrl, { allowLoopback = false } = {}) {
|
|
576
|
+
if (allowLoopback && (endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) {
|
|
577
577
|
return;
|
|
578
578
|
}
|
|
579
579
|
try {
|
|
@@ -585,6 +585,10 @@ function assertSafeOAuthEndpoint(endpointUrl) {
|
|
|
585
585
|
});
|
|
586
586
|
}
|
|
587
587
|
}
|
|
588
|
+
function getTrustedLoopbackOrigin(authorizationServerUrl) {
|
|
589
|
+
const url = new URL(authorizationServerUrl);
|
|
590
|
+
return isOAuthLoopbackHost(url.hostname) ? url.origin : void 0;
|
|
591
|
+
}
|
|
588
592
|
function createAuthorizationServerInformation(authorizationServerUrl, metadata) {
|
|
589
593
|
return {
|
|
590
594
|
authorizationServerUrl: normalizeUrl(authorizationServerUrl),
|
|
@@ -724,13 +728,27 @@ function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
|
|
|
724
728
|
}
|
|
725
729
|
return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
726
730
|
}
|
|
727
|
-
async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
731
|
+
async function fetchWithCorsRetry(url, headers, fetchFn = fetch, trustedOrigin) {
|
|
728
732
|
try {
|
|
729
|
-
return await
|
|
733
|
+
return await (0, import_provider_utils2.fetchWithValidatedRedirects)({
|
|
734
|
+
url: url.href,
|
|
735
|
+
fetch: async (input, init) => (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
736
|
+
url: input,
|
|
737
|
+
init: {
|
|
738
|
+
...init,
|
|
739
|
+
headers: new Headers(init == null ? void 0 : init.headers).has("MCP-Protocol-Version") ? headers : void 0
|
|
740
|
+
},
|
|
741
|
+
fetch: fetchFn,
|
|
742
|
+
trustedOrigin,
|
|
743
|
+
redirect: "manual"
|
|
744
|
+
}),
|
|
745
|
+
headers,
|
|
746
|
+
trustedOrigin
|
|
747
|
+
});
|
|
730
748
|
} catch (error) {
|
|
731
749
|
if (error instanceof TypeError) {
|
|
732
750
|
if (headers) {
|
|
733
|
-
return fetchWithCorsRetry(url, void 0, fetchFn);
|
|
751
|
+
return fetchWithCorsRetry(url, void 0, fetchFn, trustedOrigin);
|
|
734
752
|
} else {
|
|
735
753
|
return void 0;
|
|
736
754
|
}
|
|
@@ -738,11 +756,11 @@ async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
|
738
756
|
throw error;
|
|
739
757
|
}
|
|
740
758
|
}
|
|
741
|
-
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
|
|
759
|
+
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch, trustedOrigin) {
|
|
742
760
|
const headers = {
|
|
743
761
|
"MCP-Protocol-Version": protocolVersion
|
|
744
762
|
};
|
|
745
|
-
return await fetchWithCorsRetry(url, headers, fetchFn);
|
|
763
|
+
return await fetchWithCorsRetry(url, headers, fetchFn, trustedOrigin);
|
|
746
764
|
}
|
|
747
765
|
function shouldAttemptFallback(response, pathname) {
|
|
748
766
|
return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
|
|
@@ -759,10 +777,20 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o
|
|
|
759
777
|
url = new URL(wellKnownPath, (_b3 = opts == null ? void 0 : opts.metadataServerUrl) != null ? _b3 : issuer);
|
|
760
778
|
url.search = issuer.search;
|
|
761
779
|
}
|
|
762
|
-
let response = await tryMetadataDiscovery(
|
|
780
|
+
let response = await tryMetadataDiscovery(
|
|
781
|
+
url,
|
|
782
|
+
protocolVersion,
|
|
783
|
+
fetchFn,
|
|
784
|
+
opts == null ? void 0 : opts.trustedOrigin
|
|
785
|
+
);
|
|
763
786
|
if (!(opts == null ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
|
|
764
787
|
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
|
|
765
|
-
response = await tryMetadataDiscovery(
|
|
788
|
+
response = await tryMetadataDiscovery(
|
|
789
|
+
rootUrl,
|
|
790
|
+
protocolVersion,
|
|
791
|
+
fetchFn,
|
|
792
|
+
opts == null ? void 0 : opts.trustedOrigin
|
|
793
|
+
);
|
|
766
794
|
}
|
|
767
795
|
return response;
|
|
768
796
|
}
|
|
@@ -773,7 +801,10 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
|
|
|
773
801
|
fetchFn,
|
|
774
802
|
{
|
|
775
803
|
protocolVersion: opts == null ? void 0 : opts.protocolVersion,
|
|
776
|
-
metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl
|
|
804
|
+
metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl,
|
|
805
|
+
// The configured MCP server is trusted as a request target. Redirects
|
|
806
|
+
// crossing its origin are still validated before they are followed.
|
|
807
|
+
trustedOrigin: new URL(serverUrl).origin
|
|
777
808
|
}
|
|
778
809
|
);
|
|
779
810
|
if (!response || response.status === 404) {
|
|
@@ -846,13 +877,19 @@ function assertMetadataIssuerMatches(metadata, expectedIssuer) {
|
|
|
846
877
|
}
|
|
847
878
|
async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
848
879
|
fetchFn = fetch,
|
|
849
|
-
protocolVersion = LATEST_PROTOCOL_VERSION
|
|
880
|
+
protocolVersion = LATEST_PROTOCOL_VERSION,
|
|
881
|
+
trustedOrigin
|
|
850
882
|
} = {}) {
|
|
851
883
|
var _a3;
|
|
852
884
|
const headers = { "MCP-Protocol-Version": protocolVersion };
|
|
853
885
|
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
854
886
|
for (const { url: endpointUrl, type, expectedIssuer } of urlsToTry) {
|
|
855
|
-
const response = await fetchWithCorsRetry(
|
|
887
|
+
const response = await fetchWithCorsRetry(
|
|
888
|
+
endpointUrl,
|
|
889
|
+
headers,
|
|
890
|
+
fetchFn,
|
|
891
|
+
trustedOrigin
|
|
892
|
+
);
|
|
856
893
|
if (!response) {
|
|
857
894
|
continue;
|
|
858
895
|
}
|
|
@@ -1018,7 +1055,10 @@ async function exchangeAuthorization(authorizationServerUrl, {
|
|
|
1018
1055
|
var _a3;
|
|
1019
1056
|
const grantType = "authorization_code";
|
|
1020
1057
|
const tokenUrl = (metadata == null ? void 0 : metadata.token_endpoint) ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
|
|
1021
|
-
|
|
1058
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
1059
|
+
assertSafeOAuthEndpoint(tokenUrl, {
|
|
1060
|
+
allowLoopback: tokenUrl.origin === trustedOrigin
|
|
1061
|
+
});
|
|
1022
1062
|
if ((metadata == null ? void 0 : metadata.grant_types_supported) && !metadata.grant_types_supported.includes(grantType)) {
|
|
1023
1063
|
throw new Error(
|
|
1024
1064
|
`Incompatible auth server: does not support grant type ${grantType}`
|
|
@@ -1052,11 +1092,15 @@ async function exchangeAuthorization(authorizationServerUrl, {
|
|
|
1052
1092
|
if (resource) {
|
|
1053
1093
|
params.set("resource", resourceUrlStripSlash(resource));
|
|
1054
1094
|
}
|
|
1055
|
-
const response = await (
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1095
|
+
const response = await (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
1096
|
+
url: tokenUrl,
|
|
1097
|
+
init: {
|
|
1098
|
+
method: "POST",
|
|
1099
|
+
headers,
|
|
1100
|
+
body: params
|
|
1101
|
+
},
|
|
1102
|
+
fetch: fetchFn,
|
|
1103
|
+
trustedOrigin
|
|
1060
1104
|
});
|
|
1061
1105
|
if (!response.ok) {
|
|
1062
1106
|
throw await parseErrorResponse(response);
|
|
@@ -1084,7 +1128,10 @@ async function refreshAuthorization(authorizationServerUrl, {
|
|
|
1084
1128
|
} else {
|
|
1085
1129
|
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
1086
1130
|
}
|
|
1087
|
-
|
|
1131
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
1132
|
+
assertSafeOAuthEndpoint(tokenUrl, {
|
|
1133
|
+
allowLoopback: tokenUrl.origin === trustedOrigin
|
|
1134
|
+
});
|
|
1088
1135
|
const headers = new Headers({
|
|
1089
1136
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
1090
1137
|
Accept: "application/json"
|
|
@@ -1111,11 +1158,15 @@ async function refreshAuthorization(authorizationServerUrl, {
|
|
|
1111
1158
|
if (resource) {
|
|
1112
1159
|
params.set("resource", resourceUrlStripSlash(resource));
|
|
1113
1160
|
}
|
|
1114
|
-
const response = await (
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1161
|
+
const response = await (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
1162
|
+
url: tokenUrl,
|
|
1163
|
+
init: {
|
|
1164
|
+
method: "POST",
|
|
1165
|
+
headers,
|
|
1166
|
+
body: params
|
|
1167
|
+
},
|
|
1168
|
+
fetch: fetchFn,
|
|
1169
|
+
trustedOrigin
|
|
1119
1170
|
});
|
|
1120
1171
|
if (!response.ok) {
|
|
1121
1172
|
throw await parseErrorResponse(response);
|
|
@@ -1141,14 +1192,21 @@ async function registerClient(authorizationServerUrl, {
|
|
|
1141
1192
|
} else {
|
|
1142
1193
|
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
1143
1194
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1195
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
1196
|
+
assertSafeOAuthEndpoint(registrationUrl, {
|
|
1197
|
+
allowLoopback: registrationUrl.origin === trustedOrigin
|
|
1198
|
+
});
|
|
1199
|
+
const response = await (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
1200
|
+
url: registrationUrl,
|
|
1201
|
+
init: {
|
|
1202
|
+
method: "POST",
|
|
1203
|
+
headers: {
|
|
1204
|
+
"Content-Type": "application/json"
|
|
1205
|
+
},
|
|
1206
|
+
body: JSON.stringify(clientMetadata)
|
|
1149
1207
|
},
|
|
1150
|
-
|
|
1151
|
-
|
|
1208
|
+
fetch: fetchFn,
|
|
1209
|
+
trustedOrigin
|
|
1152
1210
|
});
|
|
1153
1211
|
if (!response.ok) {
|
|
1154
1212
|
throw await parseErrorResponse(response);
|
|
@@ -1231,6 +1289,14 @@ async function authInternal(provider, {
|
|
|
1231
1289
|
if (!authorizationServerUrl) {
|
|
1232
1290
|
authorizationServerUrl = (_a3 = callbackAuthorizationServerInformation == null ? void 0 : callbackAuthorizationServerInformation.authorizationServerUrl) != null ? _a3 : serverUrl;
|
|
1233
1291
|
}
|
|
1292
|
+
const parsedServerUrl = new URL(serverUrl);
|
|
1293
|
+
const parsedAuthorizationServerUrl = new URL(authorizationServerUrl);
|
|
1294
|
+
const serverOrigin = parsedServerUrl.origin;
|
|
1295
|
+
const authorizationServerOrigin = parsedAuthorizationServerUrl.origin;
|
|
1296
|
+
const trustedAuthorizationServerOrigin = authorizationServerOrigin === serverOrigin || isOAuthLoopbackHost(parsedServerUrl.hostname) && isOAuthLoopbackHost(parsedAuthorizationServerUrl.hostname) ? authorizationServerOrigin : void 0;
|
|
1297
|
+
if (!trustedAuthorizationServerOrigin) {
|
|
1298
|
+
assertSafeOAuthEndpoint(new URL(authorizationServerUrl));
|
|
1299
|
+
}
|
|
1234
1300
|
const resource = await selectResourceURL(
|
|
1235
1301
|
serverUrl,
|
|
1236
1302
|
provider,
|
|
@@ -1244,7 +1310,8 @@ async function authInternal(provider, {
|
|
|
1244
1310
|
const metadata = await discoverAuthorizationServerMetadata(
|
|
1245
1311
|
authorizationServerUrl,
|
|
1246
1312
|
{
|
|
1247
|
-
fetchFn
|
|
1313
|
+
fetchFn,
|
|
1314
|
+
trustedOrigin: trustedAuthorizationServerOrigin
|
|
1248
1315
|
}
|
|
1249
1316
|
);
|
|
1250
1317
|
const currentAuthorizationServerInformation = createAuthorizationServerInformation(authorizationServerUrl, metadata);
|