@ai-sdk/mcp 0.0.38 → 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 +12 -0
- package/dist/index.js +114 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +120 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -437,7 +437,12 @@ function checkResourceAllowed({
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
// src/tool/oauth.ts
|
|
440
|
-
import {
|
|
440
|
+
import {
|
|
441
|
+
fetchWithValidatedEndpoint,
|
|
442
|
+
fetchWithValidatedRedirects,
|
|
443
|
+
parseJSON as parseJSON2,
|
|
444
|
+
validateDownloadUrl
|
|
445
|
+
} from "@ai-sdk/provider-utils";
|
|
441
446
|
var UnauthorizedError = class extends Error {
|
|
442
447
|
constructor(message = "Unauthorized") {
|
|
443
448
|
super(message);
|
|
@@ -471,13 +476,48 @@ function buildWellKnownPath(wellKnownPrefix, pathname = "", options = {}) {
|
|
|
471
476
|
}
|
|
472
477
|
return options.prependPathname ? `${pathname}/.well-known/${wellKnownPrefix}` : `/.well-known/${wellKnownPrefix}${pathname}`;
|
|
473
478
|
}
|
|
474
|
-
|
|
479
|
+
function isOAuthLoopbackHost(hostname) {
|
|
480
|
+
const normalized = hostname.toLowerCase().replace(/\.+$/, "");
|
|
481
|
+
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "127.0.0.1" || normalized === "[::1]" || normalized === "::1";
|
|
482
|
+
}
|
|
483
|
+
function assertSafeOAuthEndpoint(endpointUrl, { allowLoopback = false } = {}) {
|
|
484
|
+
if (allowLoopback && (endpointUrl.protocol === "http:" || endpointUrl.protocol === "https:") && isOAuthLoopbackHost(endpointUrl.hostname)) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
475
487
|
try {
|
|
476
|
-
|
|
488
|
+
validateDownloadUrl(endpointUrl.href);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
throw new MCPClientOAuthError({
|
|
491
|
+
message: `OAuth endpoint URL is not allowed: ${endpointUrl.href}`,
|
|
492
|
+
cause: error
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
function getTrustedLoopbackOrigin(authorizationServerUrl) {
|
|
497
|
+
const url = new URL(authorizationServerUrl);
|
|
498
|
+
return isOAuthLoopbackHost(url.hostname) ? url.origin : void 0;
|
|
499
|
+
}
|
|
500
|
+
async function fetchWithCorsRetry(url, headers, fetchFn = fetch, trustedOrigin) {
|
|
501
|
+
try {
|
|
502
|
+
return await fetchWithValidatedRedirects({
|
|
503
|
+
url: url.href,
|
|
504
|
+
fetch: async (input, init) => fetchWithValidatedEndpoint({
|
|
505
|
+
url: input,
|
|
506
|
+
init: {
|
|
507
|
+
...init,
|
|
508
|
+
headers: new Headers(init == null ? void 0 : init.headers).has("MCP-Protocol-Version") ? headers : void 0
|
|
509
|
+
},
|
|
510
|
+
fetch: fetchFn,
|
|
511
|
+
trustedOrigin,
|
|
512
|
+
redirect: "manual"
|
|
513
|
+
}),
|
|
514
|
+
headers,
|
|
515
|
+
trustedOrigin
|
|
516
|
+
});
|
|
477
517
|
} catch (error) {
|
|
478
518
|
if (error instanceof TypeError) {
|
|
479
519
|
if (headers) {
|
|
480
|
-
return fetchWithCorsRetry(url, void 0, fetchFn);
|
|
520
|
+
return fetchWithCorsRetry(url, void 0, fetchFn, trustedOrigin);
|
|
481
521
|
} else {
|
|
482
522
|
return void 0;
|
|
483
523
|
}
|
|
@@ -485,11 +525,11 @@ async function fetchWithCorsRetry(url, headers, fetchFn = fetch) {
|
|
|
485
525
|
throw error;
|
|
486
526
|
}
|
|
487
527
|
}
|
|
488
|
-
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch) {
|
|
528
|
+
async function tryMetadataDiscovery(url, protocolVersion, fetchFn = fetch, trustedOrigin) {
|
|
489
529
|
const headers = {
|
|
490
530
|
"MCP-Protocol-Version": protocolVersion
|
|
491
531
|
};
|
|
492
|
-
return await fetchWithCorsRetry(url, headers, fetchFn);
|
|
532
|
+
return await fetchWithCorsRetry(url, headers, fetchFn, trustedOrigin);
|
|
493
533
|
}
|
|
494
534
|
function shouldAttemptFallback(response, pathname) {
|
|
495
535
|
return !response || response.status >= 400 && response.status < 500 && pathname !== "/";
|
|
@@ -506,10 +546,20 @@ async function discoverMetadataWithFallback(serverUrl, wellKnownType, fetchFn, o
|
|
|
506
546
|
url = new URL(wellKnownPath, (_b3 = opts == null ? void 0 : opts.metadataServerUrl) != null ? _b3 : issuer);
|
|
507
547
|
url.search = issuer.search;
|
|
508
548
|
}
|
|
509
|
-
let response = await tryMetadataDiscovery(
|
|
549
|
+
let response = await tryMetadataDiscovery(
|
|
550
|
+
url,
|
|
551
|
+
protocolVersion,
|
|
552
|
+
fetchFn,
|
|
553
|
+
opts == null ? void 0 : opts.trustedOrigin
|
|
554
|
+
);
|
|
510
555
|
if (!(opts == null ? void 0 : opts.metadataUrl) && shouldAttemptFallback(response, issuer.pathname)) {
|
|
511
556
|
const rootUrl = new URL(`/.well-known/${wellKnownType}`, issuer);
|
|
512
|
-
response = await tryMetadataDiscovery(
|
|
557
|
+
response = await tryMetadataDiscovery(
|
|
558
|
+
rootUrl,
|
|
559
|
+
protocolVersion,
|
|
560
|
+
fetchFn,
|
|
561
|
+
opts == null ? void 0 : opts.trustedOrigin
|
|
562
|
+
);
|
|
513
563
|
}
|
|
514
564
|
return response;
|
|
515
565
|
}
|
|
@@ -520,7 +570,10 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
|
|
|
520
570
|
fetchFn,
|
|
521
571
|
{
|
|
522
572
|
protocolVersion: opts == null ? void 0 : opts.protocolVersion,
|
|
523
|
-
metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl
|
|
573
|
+
metadataUrl: opts == null ? void 0 : opts.resourceMetadataUrl,
|
|
574
|
+
// The configured MCP server is trusted as a request target. Redirects
|
|
575
|
+
// crossing its origin are still validated before they are followed.
|
|
576
|
+
trustedOrigin: new URL(serverUrl).origin
|
|
524
577
|
}
|
|
525
578
|
);
|
|
526
579
|
if (!response || response.status === 404) {
|
|
@@ -577,13 +630,19 @@ function buildDiscoveryUrls(authorizationServerUrl) {
|
|
|
577
630
|
}
|
|
578
631
|
async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
579
632
|
fetchFn = fetch,
|
|
580
|
-
protocolVersion = LATEST_PROTOCOL_VERSION
|
|
633
|
+
protocolVersion = LATEST_PROTOCOL_VERSION,
|
|
634
|
+
trustedOrigin
|
|
581
635
|
} = {}) {
|
|
582
636
|
var _a3;
|
|
583
637
|
const headers = { "MCP-Protocol-Version": protocolVersion };
|
|
584
638
|
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
585
639
|
for (const { url: endpointUrl, type } of urlsToTry) {
|
|
586
|
-
const response = await fetchWithCorsRetry(
|
|
640
|
+
const response = await fetchWithCorsRetry(
|
|
641
|
+
endpointUrl,
|
|
642
|
+
headers,
|
|
643
|
+
fetchFn,
|
|
644
|
+
trustedOrigin
|
|
645
|
+
);
|
|
587
646
|
if (!response) {
|
|
588
647
|
continue;
|
|
589
648
|
}
|
|
@@ -743,6 +802,10 @@ async function exchangeAuthorization(authorizationServerUrl, {
|
|
|
743
802
|
var _a3;
|
|
744
803
|
const grantType = "authorization_code";
|
|
745
804
|
const tokenUrl = (metadata == null ? void 0 : metadata.token_endpoint) ? new URL(metadata.token_endpoint) : new URL("/token", authorizationServerUrl);
|
|
805
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
806
|
+
assertSafeOAuthEndpoint(tokenUrl, {
|
|
807
|
+
allowLoopback: tokenUrl.origin === trustedOrigin
|
|
808
|
+
});
|
|
746
809
|
if ((metadata == null ? void 0 : metadata.grant_types_supported) && !metadata.grant_types_supported.includes(grantType)) {
|
|
747
810
|
throw new Error(
|
|
748
811
|
`Incompatible auth server: does not support grant type ${grantType}`
|
|
@@ -771,10 +834,15 @@ async function exchangeAuthorization(authorizationServerUrl, {
|
|
|
771
834
|
if (resource) {
|
|
772
835
|
params.set("resource", resource.href);
|
|
773
836
|
}
|
|
774
|
-
const response = await (
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
837
|
+
const response = await fetchWithValidatedEndpoint({
|
|
838
|
+
url: tokenUrl,
|
|
839
|
+
init: {
|
|
840
|
+
method: "POST",
|
|
841
|
+
headers,
|
|
842
|
+
body: params
|
|
843
|
+
},
|
|
844
|
+
fetch: fetchFn,
|
|
845
|
+
trustedOrigin
|
|
778
846
|
});
|
|
779
847
|
if (!response.ok) {
|
|
780
848
|
throw await parseErrorResponse(response);
|
|
@@ -802,6 +870,10 @@ async function refreshAuthorization(authorizationServerUrl, {
|
|
|
802
870
|
} else {
|
|
803
871
|
tokenUrl = new URL("/token", authorizationServerUrl);
|
|
804
872
|
}
|
|
873
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
874
|
+
assertSafeOAuthEndpoint(tokenUrl, {
|
|
875
|
+
allowLoopback: tokenUrl.origin === trustedOrigin
|
|
876
|
+
});
|
|
805
877
|
const headers = new Headers({
|
|
806
878
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
807
879
|
});
|
|
@@ -822,10 +894,15 @@ async function refreshAuthorization(authorizationServerUrl, {
|
|
|
822
894
|
if (resource) {
|
|
823
895
|
params.set("resource", resource.href);
|
|
824
896
|
}
|
|
825
|
-
const response = await (
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
897
|
+
const response = await fetchWithValidatedEndpoint({
|
|
898
|
+
url: tokenUrl,
|
|
899
|
+
init: {
|
|
900
|
+
method: "POST",
|
|
901
|
+
headers,
|
|
902
|
+
body: params
|
|
903
|
+
},
|
|
904
|
+
fetch: fetchFn,
|
|
905
|
+
trustedOrigin
|
|
829
906
|
});
|
|
830
907
|
if (!response.ok) {
|
|
831
908
|
throw await parseErrorResponse(response);
|
|
@@ -851,12 +928,21 @@ async function registerClient(authorizationServerUrl, {
|
|
|
851
928
|
} else {
|
|
852
929
|
registrationUrl = new URL("/register", authorizationServerUrl);
|
|
853
930
|
}
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
931
|
+
const trustedOrigin = getTrustedLoopbackOrigin(authorizationServerUrl);
|
|
932
|
+
assertSafeOAuthEndpoint(registrationUrl, {
|
|
933
|
+
allowLoopback: registrationUrl.origin === trustedOrigin
|
|
934
|
+
});
|
|
935
|
+
const response = await fetchWithValidatedEndpoint({
|
|
936
|
+
url: registrationUrl,
|
|
937
|
+
init: {
|
|
938
|
+
method: "POST",
|
|
939
|
+
headers: {
|
|
940
|
+
"Content-Type": "application/json"
|
|
941
|
+
},
|
|
942
|
+
body: JSON.stringify(clientMetadata)
|
|
858
943
|
},
|
|
859
|
-
|
|
944
|
+
fetch: fetchFn,
|
|
945
|
+
trustedOrigin
|
|
860
946
|
});
|
|
861
947
|
if (!response.ok) {
|
|
862
948
|
throw await parseErrorResponse(response);
|
|
@@ -922,6 +1008,14 @@ async function authInternal(provider, {
|
|
|
922
1008
|
if (!authorizationServerUrl) {
|
|
923
1009
|
authorizationServerUrl = serverUrl;
|
|
924
1010
|
}
|
|
1011
|
+
const parsedServerUrl = new URL(serverUrl);
|
|
1012
|
+
const parsedAuthorizationServerUrl = new URL(authorizationServerUrl);
|
|
1013
|
+
const serverOrigin = parsedServerUrl.origin;
|
|
1014
|
+
const authorizationServerOrigin = parsedAuthorizationServerUrl.origin;
|
|
1015
|
+
const trustedAuthorizationServerOrigin = authorizationServerOrigin === serverOrigin || isOAuthLoopbackHost(parsedServerUrl.hostname) && isOAuthLoopbackHost(parsedAuthorizationServerUrl.hostname) ? authorizationServerOrigin : void 0;
|
|
1016
|
+
if (!trustedAuthorizationServerOrigin) {
|
|
1017
|
+
assertSafeOAuthEndpoint(new URL(authorizationServerUrl));
|
|
1018
|
+
}
|
|
925
1019
|
const resource = await selectResourceURL(
|
|
926
1020
|
serverUrl,
|
|
927
1021
|
provider,
|
|
@@ -930,7 +1024,8 @@ async function authInternal(provider, {
|
|
|
930
1024
|
const metadata = await discoverAuthorizationServerMetadata(
|
|
931
1025
|
authorizationServerUrl,
|
|
932
1026
|
{
|
|
933
|
-
fetchFn
|
|
1027
|
+
fetchFn,
|
|
1028
|
+
trustedOrigin: trustedAuthorizationServerOrigin
|
|
934
1029
|
}
|
|
935
1030
|
);
|
|
936
1031
|
let clientInformation = await Promise.resolve(provider.clientInformation());
|