@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 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
- return OAuthMetadataSchema.parse(await response.json());
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
- this.endpoint = new URL(data, this.url);
1384
- if (this.endpoint.origin !== this.url.origin) {
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: ${this.endpoint.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
- (_a4 = this.onmessage) == null ? void 0 : _a4.call(this, message);
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
- (_b4 = this.onerror) == null ? void 0 : _b4.call(this, e);
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
- (_c2 = this.onerror) == null ? void 0 : _c2.call(this, error);
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 in schemas)) {
2229
+ if (schemas !== "automatic" && !Object.prototype.hasOwnProperty.call(schemas, name3)) {
2203
2230
  continue;
2204
2231
  }
2205
2232
  const self = this;