@ai-sdk/mcp 1.0.48 → 1.0.50
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 +25 -0
- package/dist/index.js +43 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/tool/mcp-client.ts +4 -1
- package/src/tool/mcp-sse-transport.ts +13 -3
- package/src/tool/oauth.ts +30 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @ai-sdk/mcp
|
|
2
2
|
|
|
3
|
+
## 1.0.50
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ac40275: fix(mcp): validate oauth metadata issuer during discovery
|
|
8
|
+
|
|
9
|
+
## 1.0.49
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 3e8d9ba: fix(mcp): lock first sse endpoint received via event
|
|
14
|
+
- 4fa7354: fix(mcp): prevent prototype-named tools from bypassing the `schemas` allowlist
|
|
15
|
+
|
|
16
|
+
When using `client.tools({ schemas })` to expose only an explicitly allowed
|
|
17
|
+
subset of an MCP server's tools, the allowlist check used the `in` operator,
|
|
18
|
+
which also matches inherited `Object.prototype` properties. A server-advertised
|
|
19
|
+
tool named `constructor`, `toString`, `__proto__`, etc. would pass the check
|
|
20
|
+
even though the developer never defined it in `schemas`, and was then exposed to
|
|
21
|
+
the model and executable. The check now uses `Object.hasOwn`, so only
|
|
22
|
+
explicitly defined tools are returned.
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [bfa5864]
|
|
25
|
+
- Updated dependencies [f42aa79]
|
|
26
|
+
- @ai-sdk/provider-utils@4.0.29
|
|
27
|
+
|
|
3
28
|
## 1.0.48
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -722,15 +722,18 @@ async function discoverOAuthProtectedResourceMetadata(serverUrl, opts, fetchFn =
|
|
|
722
722
|
function buildDiscoveryUrls(authorizationServerUrl) {
|
|
723
723
|
const url = typeof authorizationServerUrl === "string" ? new URL(authorizationServerUrl) : authorizationServerUrl;
|
|
724
724
|
const hasPath = url.pathname !== "/";
|
|
725
|
+
const rootIssuer = url.origin;
|
|
725
726
|
const urlsToTry = [];
|
|
726
727
|
if (!hasPath) {
|
|
727
728
|
urlsToTry.push({
|
|
728
729
|
url: new URL("/.well-known/oauth-authorization-server", url.origin),
|
|
729
|
-
type: "oauth"
|
|
730
|
+
type: "oauth",
|
|
731
|
+
expectedIssuer: rootIssuer
|
|
730
732
|
});
|
|
731
733
|
urlsToTry.push({
|
|
732
734
|
url: new URL("/.well-known/openid-configuration", url.origin),
|
|
733
|
-
type: "oidc"
|
|
735
|
+
type: "oidc",
|
|
736
|
+
expectedIssuer: rootIssuer
|
|
734
737
|
});
|
|
735
738
|
return urlsToTry;
|
|
736
739
|
}
|
|
@@ -738,27 +741,39 @@ function buildDiscoveryUrls(authorizationServerUrl) {
|
|
|
738
741
|
if (pathname.endsWith("/")) {
|
|
739
742
|
pathname = pathname.slice(0, -1);
|
|
740
743
|
}
|
|
744
|
+
const pathIssuer = `${url.origin}${pathname}`;
|
|
741
745
|
urlsToTry.push({
|
|
742
746
|
url: new URL(
|
|
743
747
|
`/.well-known/oauth-authorization-server${pathname}`,
|
|
744
748
|
url.origin
|
|
745
749
|
),
|
|
746
|
-
type: "oauth"
|
|
750
|
+
type: "oauth",
|
|
751
|
+
expectedIssuer: pathIssuer
|
|
747
752
|
});
|
|
748
753
|
urlsToTry.push({
|
|
749
754
|
url: new URL("/.well-known/oauth-authorization-server", url.origin),
|
|
750
|
-
type: "oauth"
|
|
755
|
+
type: "oauth",
|
|
756
|
+
expectedIssuer: rootIssuer
|
|
751
757
|
});
|
|
752
758
|
urlsToTry.push({
|
|
753
759
|
url: new URL(`/.well-known/openid-configuration${pathname}`, url.origin),
|
|
754
|
-
type: "oidc"
|
|
760
|
+
type: "oidc",
|
|
761
|
+
expectedIssuer: pathIssuer
|
|
755
762
|
});
|
|
756
763
|
urlsToTry.push({
|
|
757
764
|
url: new URL(`${pathname}/.well-known/openid-configuration`, url.origin),
|
|
758
|
-
type: "oidc"
|
|
765
|
+
type: "oidc",
|
|
766
|
+
expectedIssuer: pathIssuer
|
|
759
767
|
});
|
|
760
768
|
return urlsToTry;
|
|
761
769
|
}
|
|
770
|
+
function assertMetadataIssuerMatches(metadata, expectedIssuer) {
|
|
771
|
+
if (metadata.issuer !== expectedIssuer) {
|
|
772
|
+
throw new MCPClientOAuthError({
|
|
773
|
+
message: `OAuth authorization server metadata issuer ${metadata.issuer} does not match expected issuer ${expectedIssuer}`
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
}
|
|
762
777
|
async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
763
778
|
fetchFn = fetch,
|
|
764
779
|
protocolVersion = LATEST_PROTOCOL_VERSION
|
|
@@ -766,7 +781,7 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
|
766
781
|
var _a3;
|
|
767
782
|
const headers = { "MCP-Protocol-Version": protocolVersion };
|
|
768
783
|
const urlsToTry = buildDiscoveryUrls(authorizationServerUrl);
|
|
769
|
-
for (const { url: endpointUrl, type } of urlsToTry) {
|
|
784
|
+
for (const { url: endpointUrl, type, expectedIssuer } of urlsToTry) {
|
|
770
785
|
const response = await fetchWithCorsRetry(endpointUrl, headers, fetchFn);
|
|
771
786
|
if (!response) {
|
|
772
787
|
continue;
|
|
@@ -780,11 +795,14 @@ async function discoverAuthorizationServerMetadata(authorizationServerUrl, {
|
|
|
780
795
|
);
|
|
781
796
|
}
|
|
782
797
|
if (type === "oauth") {
|
|
783
|
-
|
|
798
|
+
const metadata = OAuthMetadataSchema.parse(await response.json());
|
|
799
|
+
assertMetadataIssuerMatches(metadata, expectedIssuer);
|
|
800
|
+
return metadata;
|
|
784
801
|
} else {
|
|
785
802
|
const metadata = OpenIdProviderDiscoveryMetadataSchema.parse(
|
|
786
803
|
await response.json()
|
|
787
804
|
);
|
|
805
|
+
assertMetadataIssuerMatches(metadata, expectedIssuer);
|
|
788
806
|
if (!((_a3 = metadata.code_challenge_methods_supported) == null ? void 0 : _a3.includes("S256"))) {
|
|
789
807
|
throw new Error(
|
|
790
808
|
`Incompatible OIDC provider at ${endpointUrl}: does not support S256 code challenge method required by MCP specification`
|
|
@@ -1365,7 +1383,7 @@ var SseMCPTransport = class {
|
|
|
1365
1383
|
const stream = response.body.pipeThrough(new TextDecoderStream()).pipeThrough(new import_provider_utils3.EventSourceParserStream());
|
|
1366
1384
|
const reader = stream.getReader();
|
|
1367
1385
|
const processEvents = async () => {
|
|
1368
|
-
var _a4, _b4, _c2;
|
|
1386
|
+
var _a4, _b4, _c2, _d2, _e2;
|
|
1369
1387
|
try {
|
|
1370
1388
|
while (true) {
|
|
1371
1389
|
const { done, value } = await reader.read();
|
|
@@ -1380,24 +1398,32 @@ var SseMCPTransport = class {
|
|
|
1380
1398
|
}
|
|
1381
1399
|
const { event, data } = value;
|
|
1382
1400
|
if (event === "endpoint") {
|
|
1383
|
-
|
|
1384
|
-
|
|
1401
|
+
if (this.endpoint) {
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
const endpoint = new URL(data, this.url);
|
|
1405
|
+
if (endpoint.origin !== this.url.origin) {
|
|
1406
|
+
this.connected = false;
|
|
1407
|
+
this.endpoint = void 0;
|
|
1408
|
+
(_a4 = this.sseConnection) == null ? void 0 : _a4.close();
|
|
1409
|
+
(_b4 = this.abortController) == null ? void 0 : _b4.abort();
|
|
1385
1410
|
throw new MCPClientError({
|
|
1386
|
-
message: `MCP SSE Transport Error: Endpoint origin does not match connection origin: ${
|
|
1411
|
+
message: `MCP SSE Transport Error: Endpoint origin does not match connection origin: ${endpoint.origin}`
|
|
1387
1412
|
});
|
|
1388
1413
|
}
|
|
1414
|
+
this.endpoint = endpoint;
|
|
1389
1415
|
this.connected = true;
|
|
1390
1416
|
resolve();
|
|
1391
1417
|
} else if (event === "message") {
|
|
1392
1418
|
try {
|
|
1393
1419
|
const message = await parseJSONRPCMessage(data);
|
|
1394
|
-
(
|
|
1420
|
+
(_c2 = this.onmessage) == null ? void 0 : _c2.call(this, message);
|
|
1395
1421
|
} catch (error) {
|
|
1396
1422
|
const e = new MCPClientError({
|
|
1397
1423
|
message: "MCP SSE Transport Error: Failed to parse message",
|
|
1398
1424
|
cause: error
|
|
1399
1425
|
});
|
|
1400
|
-
(
|
|
1426
|
+
(_d2 = this.onerror) == null ? void 0 : _d2.call(this, e);
|
|
1401
1427
|
}
|
|
1402
1428
|
}
|
|
1403
1429
|
}
|
|
@@ -1405,7 +1431,7 @@ var SseMCPTransport = class {
|
|
|
1405
1431
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1406
1432
|
return;
|
|
1407
1433
|
}
|
|
1408
|
-
(
|
|
1434
|
+
(_e2 = this.onerror) == null ? void 0 : _e2.call(this, error);
|
|
1409
1435
|
reject(error);
|
|
1410
1436
|
}
|
|
1411
1437
|
};
|
|
@@ -1427,6 +1453,7 @@ var SseMCPTransport = class {
|
|
|
1427
1453
|
async close() {
|
|
1428
1454
|
var _a3, _b3, _c;
|
|
1429
1455
|
this.connected = false;
|
|
1456
|
+
this.endpoint = void 0;
|
|
1430
1457
|
(_a3 = this.sseConnection) == null ? void 0 : _a3.close();
|
|
1431
1458
|
(_b3 = this.abortController) == null ? void 0 : _b3.abort();
|
|
1432
1459
|
(_c = this.onclose) == null ? void 0 : _c.call(this);
|
|
@@ -2199,7 +2226,7 @@ var DefaultMCPClient = class {
|
|
|
2199
2226
|
_meta
|
|
2200
2227
|
} of definitions.tools) {
|
|
2201
2228
|
const resolvedTitle = title != null ? title : annotations == null ? void 0 : annotations.title;
|
|
2202
|
-
if (schemas !== "automatic" && !(name3
|
|
2229
|
+
if (schemas !== "automatic" && !Object.prototype.hasOwnProperty.call(schemas, name3)) {
|
|
2203
2230
|
continue;
|
|
2204
2231
|
}
|
|
2205
2232
|
const self = this;
|