@ai-sdk/mcp 0.0.37 → 0.0.39
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 +18 -0
- package/dist/index.js +115 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @ai-sdk/mcp
|
|
2
2
|
|
|
3
|
+
## 0.0.39
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2b1c2df: fix(mcp): reject private OAuth endpoints before sending credentials
|
|
8
|
+
- 2b1c2df: 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.
|
|
9
|
+
- 2b1c2df: fix(mcp): prevent SSRF in OAuth metadata discovery
|
|
10
|
+
- Updated dependencies [2b1c2df]
|
|
11
|
+
- Updated dependencies [2b1c2df]
|
|
12
|
+
- Updated dependencies [2b1c2df]
|
|
13
|
+
- @ai-sdk/provider-utils@3.0.41
|
|
14
|
+
|
|
15
|
+
## 0.0.38
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 58d303f: Preserve prototype-named tools, providers, and provider metadata as own properties without changing lookup object prototypes. Prevent inherited names from resolving as registered providers or causing image metadata aggregation to fail.
|
|
20
|
+
|
|
3
21
|
## 0.0.37
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -503,13 +503,48 @@ function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
|
|
|
503
503
|
}
|
|
504
504
|
return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
505
505
|
}
|
|
506
|
-
|
|
506
|
+
function isOAuthLoopbackHost(hostname) {
|
|
507
|
+
const normalized = hostname.toLowerCase().replace(/\.+$/, "");
|
|
508
|
+
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "127.0.0.1" || normalized === "[::1]" || normalized === "::1";
|
|
509
|
+
}
|
|
510
|
+
function assertSafeOAuthEndpoint(endpointUrl, { allowLoopback = false } = {}) {
|
|
511
|
+
if (allowLoopback && (endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
try {
|
|
515
|
+
(0, import_provider_utils2.validateDownloadUrl)(endpointUrl.href);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
throw new MCPClientOAuthError({
|
|
518
|
+
message: `OAuth endpoint URL is not allowed: ${endpointUrl.href}`,
|
|
519
|
+
cause: error
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
function getTrustedLoopbackOrigin(authorizationServerUrl) {
|
|
524
|
+
const url = new URL(authorizationServerUrl);
|
|
525
|
+
return isOAuthLoopbackHost(url.hostname) ? url.origin : void 0;
|
|
526
|
+
}
|
|
527
|
+
async function fetchWithCorsRetry(url, headers, fetchFn = fetch, trustedOrigin) {
|
|
507
528
|
try {
|
|
508
|
-
return await
|
|
529
|
+
return await (0, import_provider_utils2.fetchWithValidatedRedirects)({
|
|
530
|
+
url: url.href,
|
|
531
|
+
fetch: async (input, init) => (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
532
|
+
url: input,
|
|
533
|
+
init: {
|
|
534
|
+
...init,
|
|
535
|
+
headers: new Headers(init == null ? void 0 : init.headers).has("MCP-Protocol-Version") ? headers : void 0
|
|
536
|
+
},
|
|
537
|
+
fetch: fetchFn,
|
|
538
|
+
trustedOrigin,
|
|
539
|
+
redirect: "manual"
|
|
540
|
+
}),
|
|
541
|
+
headers,
|
|
542
|
+
trustedOrigin
|
|
543
|
+
});
|
|
509
544
|
} catch (error) {
|
|
510
545
|
if (error instanceof TypeError) {
|
|
511
546
|
if (headers) {
|
|
512
|
-
return fetchWithCorsRetry(url, void 0, fetchFn);
|
|
547
|
+
return fetchWithCorsRetry(url, void 0, fetchFn, trustedOrigin);
|
|
513
548
|
} else {
|
|
514
549
|
return void 0;
|
|
515
550
|
}
|
|
@@ -517,11 +552,11 @@ async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
|
517
552
|
throw error;
|
|
518
553
|
}
|
|
519
554
|
}
|
|
520
|
-
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
|
|
555
|
+
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch, trustedOrigin) {
|
|
521
556
|
const headers = {
|
|
522
557
|
"MCP-Protocol-Version": protocolVersion
|
|
523
558
|
};
|
|
524
|
-
return await fetchWithCorsRetry(url, headers, fetchFn);
|
|
559
|
+
return await fetchWithCorsRetry(url, headers, fetchFn, trustedOrigin);
|
|
525
560
|
}
|
|
526
561
|
function shouldAttemptFallback(response, pathname) {
|
|
527
562
|
return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
|
|
@@ -538,10 +573,20 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o
|
|
|
538
573
|
url = new URL(wellKnownPath, (_b3 = opts == null ? void 0 : opts.metadataServerUrl) != null ? _b3 : issuer);
|
|
539
574
|
url.search = issuer.search;
|
|
540
575
|
}
|
|
541
|
-
let response = await tryMetadataDiscovery(
|
|
576
|
+
let response = await tryMetadataDiscovery(
|
|
577
|
+
url,
|
|
578
|
+
protocolVersion,
|
|
579
|
+
fetchFn,
|
|
580
|
+
opts == null ? void 0 : opts.trustedOrigin
|
|
581
|
+
);
|
|
542
582
|
if (!(opts == null ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
|
|
543
583
|
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
|
|
544
|
-
response = await tryMetadataDiscovery(
|
|
584
|
+
response = await tryMetadataDiscovery(
|
|
585
|
+
rootUrl,
|
|
586
|
+
protocolVersion,
|
|
587
|
+
fetchFn,
|
|
588
|
+
opts == null ? void 0 : opts.trustedOrigin
|
|
589
|
+
);
|
|
545
590
|
}
|
|
546
591
|
return response;
|
|
547
592
|
}
|
|
@@ -552,7 +597,10 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
|
|
|
552
597
|
fetchFn,
|
|
553
598
|
{
|
|
554
599
|
protocolVersion: opts == null ? void 0 : opts.protocolVersion,
|
|
555
|
-
metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl
|
|
600
|
+
metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl,
|
|
601
|
+
// The configured MCP server is trusted as a request target. Redirects
|
|
602
|
+
// crossing its origin are still validated before they are followed.
|
|
603
|
+
trustedOrigin: new URL(serverUrl).origin
|
|
556
604
|
}
|
|
557
605
|
);
|
|
558
606
|
if (!response || response.status === 404) {
|
|
@@ -609,13 +657,19 @@ function buildDiscoveryUrls(authorizationServerUrl) {
|
|
|
609
657
|
}
|
|
610
658
|
async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
611
659
|
fetchFn = fetch,
|
|
612
|
-
protocolVersion = LATEST_PROTOCOL_VERSION
|
|
660
|
+
protocolVersion = LATEST_PROTOCOL_VERSION,
|
|
661
|
+
trustedOrigin
|
|
613
662
|
} = {}) {
|
|
614
663
|
var _a3;
|
|
615
664
|
const headers = { "MCP-Protocol-Version": protocolVersion };
|
|
616
665
|
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
617
666
|
for (const { url: endpointUrl, type } of urlsToTry) {
|
|
618
|
-
const response = await fetchWithCorsRetry(
|
|
667
|
+
const response = await fetchWithCorsRetry(
|
|
668
|
+
endpointUrl,
|
|
669
|
+
headers,
|
|
670
|
+
fetchFn,
|
|
671
|
+
trustedOrigin
|
|
672
|
+
);
|
|
619
673
|
if (!response) {
|
|
620
674
|
continue;
|
|
621
675
|
}
|
|
@@ -775,6 +829,10 @@ async function exchangeAuthorization(authorizationServerUrl, {
|
|
|
775
829
|
var _a3;
|
|
776
830
|
const grantType = "authorization_code";
|
|
777
831
|
const tokenUrl = (metadata == null ? void 0 : metadata.token_endpoint) ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
|
|
832
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
833
|
+
assertSafeOAuthEndpoint(tokenUrl, {
|
|
834
|
+
allowLoopback: tokenUrl.origin === trustedOrigin
|
|
835
|
+
});
|
|
778
836
|
if ((metadata == null ? void 0 : metadata.grant_types_supported) && !metadata.grant_types_supported.includes(grantType)) {
|
|
779
837
|
throw new Error(
|
|
780
838
|
`Incompatible auth server: does not support grant type ${grantType}`
|
|
@@ -803,10 +861,15 @@ async function exchangeAuthorization(authorizationServerUrl, {
|
|
|
803
861
|
if (resource) {
|
|
804
862
|
params.set("resource", resource.href);
|
|
805
863
|
}
|
|
806
|
-
const response = await (
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
864
|
+
const response = await (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
865
|
+
url: tokenUrl,
|
|
866
|
+
init: {
|
|
867
|
+
method: "POST",
|
|
868
|
+
headers,
|
|
869
|
+
body: params
|
|
870
|
+
},
|
|
871
|
+
fetch: fetchFn,
|
|
872
|
+
trustedOrigin
|
|
810
873
|
});
|
|
811
874
|
if (!response.ok) {
|
|
812
875
|
throw await parseErrorResponse(response);
|
|
@@ -834,6 +897,10 @@ async function refreshAuthorization(authorizationServerUrl, {
|
|
|
834
897
|
} else {
|
|
835
898
|
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
836
899
|
}
|
|
900
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
901
|
+
assertSafeOAuthEndpoint(tokenUrl, {
|
|
902
|
+
allowLoopback: tokenUrl.origin === trustedOrigin
|
|
903
|
+
});
|
|
837
904
|
const headers = new Headers({
|
|
838
905
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
839
906
|
});
|
|
@@ -854,10 +921,15 @@ async function refreshAuthorization(authorizationServerUrl, {
|
|
|
854
921
|
if (resource) {
|
|
855
922
|
params.set("resource", resource.href);
|
|
856
923
|
}
|
|
857
|
-
const response = await (
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
924
|
+
const response = await (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
925
|
+
url: tokenUrl,
|
|
926
|
+
init: {
|
|
927
|
+
method: "POST",
|
|
928
|
+
headers,
|
|
929
|
+
body: params
|
|
930
|
+
},
|
|
931
|
+
fetch: fetchFn,
|
|
932
|
+
trustedOrigin
|
|
861
933
|
});
|
|
862
934
|
if (!response.ok) {
|
|
863
935
|
throw await parseErrorResponse(response);
|
|
@@ -883,12 +955,21 @@ async function registerClient(authorizationServerUrl, {
|
|
|
883
955
|
} else {
|
|
884
956
|
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
885
957
|
}
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
958
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
959
|
+
assertSafeOAuthEndpoint(registrationUrl, {
|
|
960
|
+
allowLoopback: registrationUrl.origin === trustedOrigin
|
|
961
|
+
});
|
|
962
|
+
const response = await (0, import_provider_utils2.fetchWithValidatedEndpoint)({
|
|
963
|
+
url: registrationUrl,
|
|
964
|
+
init: {
|
|
965
|
+
method: "POST",
|
|
966
|
+
headers: {
|
|
967
|
+
"Content-Type": "application/json"
|
|
968
|
+
},
|
|
969
|
+
body: JSON.stringify(clientMetadata)
|
|
890
970
|
},
|
|
891
|
-
|
|
971
|
+
fetch: fetchFn,
|
|
972
|
+
trustedOrigin
|
|
892
973
|
});
|
|
893
974
|
if (!response.ok) {
|
|
894
975
|
throw await parseErrorResponse(response);
|
|
@@ -954,6 +1035,14 @@ async function authInternal(provider, {
|
|
|
954
1035
|
if (!authorizationServerUrl) {
|
|
955
1036
|
authorizationServerUrl = serverUrl;
|
|
956
1037
|
}
|
|
1038
|
+
const parsedServerUrl = new URL(serverUrl);
|
|
1039
|
+
const parsedAuthorizationServerUrl = new URL(authorizationServerUrl);
|
|
1040
|
+
const serverOrigin = parsedServerUrl.origin;
|
|
1041
|
+
const authorizationServerOrigin = parsedAuthorizationServerUrl.origin;
|
|
1042
|
+
const trustedAuthorizationServerOrigin = authorizationServerOrigin === serverOrigin || isOAuthLoopbackHost(parsedServerUrl.hostname) && isOAuthLoopbackHost(parsedAuthorizationServerUrl.hostname) ? authorizationServerOrigin : void 0;
|
|
1043
|
+
if (!trustedAuthorizationServerOrigin) {
|
|
1044
|
+
assertSafeOAuthEndpoint(new URL(authorizationServerUrl));
|
|
1045
|
+
}
|
|
957
1046
|
const resource = await selectResourceURL(
|
|
958
1047
|
serverUrl,
|
|
959
1048
|
provider,
|
|
@@ -962,7 +1051,8 @@ async function authInternal(provider, {
|
|
|
962
1051
|
const metadata = await discoverAuthorizationServerMetadata(
|
|
963
1052
|
authorizationServerUrl,
|
|
964
1053
|
{
|
|
965
|
-
fetchFn
|
|
1054
|
+
fetchFn,
|
|
1055
|
+
trustedOrigin: trustedAuthorizationServerOrigin
|
|
966
1056
|
}
|
|
967
1057
|
);
|
|
968
1058
|
let clientInformation = await Promise.resolve(provider.clientInformation());
|
|
@@ -1876,7 +1966,7 @@ var DefaultMCPClient = class {
|
|
|
1876
1966
|
schemas = "automatic"
|
|
1877
1967
|
} = {}) {
|
|
1878
1968
|
var _a3;
|
|
1879
|
-
const tools =
|
|
1969
|
+
const tools = /* @__PURE__ */ Object.create(null);
|
|
1880
1970
|
let listToolsResult = await this.listTools();
|
|
1881
1971
|
const toolDefinitions = [...listToolsResult.tools];
|
|
1882
1972
|
while (listToolsResult.nextCursor != null) {
|