@ai-sdk/mcp 2.0.0-beta.2 → 2.0.0-beta.20

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,138 @@
1
1
  # @ai-sdk/mcp
2
2
 
3
+ ## 2.0.0-beta.20
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [34bd95d]
8
+ - Updated dependencies [008271d]
9
+ - @ai-sdk/provider@4.0.0-beta.8
10
+ - @ai-sdk/provider-utils@5.0.0-beta.14
11
+
12
+ ## 2.0.0-beta.19
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [b0c2869]
17
+ - Updated dependencies [7e26e81]
18
+ - @ai-sdk/provider-utils@5.0.0-beta.13
19
+
20
+ ## 2.0.0-beta.18
21
+
22
+ ### Patch Changes
23
+
24
+ - e3ea484: fix(mcp): bypass outputSchema validation when tool returns isError
25
+
26
+ ## 2.0.0-beta.17
27
+
28
+ ### Patch Changes
29
+
30
+ - a00d1d3: feat(mcp): allow custom fetch for HTTP and SSE transports
31
+
32
+ ## 2.0.0-beta.16
33
+
34
+ ### Patch Changes
35
+
36
+ - Updated dependencies [46d1149]
37
+ - @ai-sdk/provider-utils@5.0.0-beta.12
38
+
39
+ ## 2.0.0-beta.15
40
+
41
+ ### Patch Changes
42
+
43
+ - Updated dependencies [6fd51c0]
44
+ - @ai-sdk/provider-utils@5.0.0-beta.11
45
+ - @ai-sdk/provider@4.0.0-beta.7
46
+
47
+ ## 2.0.0-beta.14
48
+
49
+ ### Patch Changes
50
+
51
+ - 1e89d62: fix(mcp): strip trailing slash from OAuth resource parameter
52
+ - Updated dependencies [c29a26f]
53
+ - @ai-sdk/provider-utils@5.0.0-beta.10
54
+ - @ai-sdk/provider@4.0.0-beta.6
55
+
56
+ ## 2.0.0-beta.13
57
+
58
+ ### Patch Changes
59
+
60
+ - Updated dependencies [2e17091]
61
+ - @ai-sdk/provider-utils@5.0.0-beta.9
62
+
63
+ ## 2.0.0-beta.12
64
+
65
+ ### Patch Changes
66
+
67
+ - Updated dependencies [986c6fd]
68
+ - Updated dependencies [493295c]
69
+ - @ai-sdk/provider-utils@5.0.0-beta.8
70
+
71
+ ## 2.0.0-beta.11
72
+
73
+ ### Patch Changes
74
+
75
+ - Updated dependencies [1f509d4]
76
+ - @ai-sdk/provider-utils@5.0.0-beta.7
77
+ - @ai-sdk/provider@4.0.0-beta.5
78
+
79
+ ## 2.0.0-beta.10
80
+
81
+ ### Patch Changes
82
+
83
+ - Updated dependencies [3887c70]
84
+ - @ai-sdk/provider-utils@5.0.0-beta.6
85
+ - @ai-sdk/provider@4.0.0-beta.4
86
+
87
+ ## 2.0.0-beta.9
88
+
89
+ ### Patch Changes
90
+
91
+ - Updated dependencies [776b617]
92
+ - @ai-sdk/provider-utils@5.0.0-beta.5
93
+ - @ai-sdk/provider@4.0.0-beta.3
94
+
95
+ ## 2.0.0-beta.8
96
+
97
+ ### Patch Changes
98
+
99
+ - Updated dependencies [61753c3]
100
+ - @ai-sdk/provider-utils@5.0.0-beta.4
101
+
102
+ ## 2.0.0-beta.7
103
+
104
+ ### Major Changes
105
+
106
+ - 23fa161: fix(mcp): setting redirect: error for MCP transport
107
+
108
+ ## 2.0.0-beta.6
109
+
110
+ ### Patch Changes
111
+
112
+ - 58c9eb1: feat(mcp): add `redirect` option to `MCPTransportConfig` for controlling HTTP redirect behavior
113
+
114
+ ## 2.0.0-beta.5
115
+
116
+ ### Patch Changes
117
+
118
+ - Updated dependencies [f7d4f01]
119
+ - @ai-sdk/provider-utils@5.0.0-beta.3
120
+ - @ai-sdk/provider@4.0.0-beta.2
121
+
122
+ ## 2.0.0-beta.4
123
+
124
+ ### Patch Changes
125
+
126
+ - Updated dependencies [5c2a5a2]
127
+ - @ai-sdk/provider@4.0.0-beta.1
128
+ - @ai-sdk/provider-utils@5.0.0-beta.2
129
+
130
+ ## 2.0.0-beta.3
131
+
132
+ ### Patch Changes
133
+
134
+ - b9b3899: fix(mcp): validate state param in oauth flow
135
+
3
136
  ## 2.0.0-beta.2
4
137
 
5
138
  ### Patch Changes
@@ -213,15 +346,15 @@
213
346
  This change replaces
214
347
 
215
348
  ```ts
216
- import { experimental_createMCPClient } from 'ai';
217
- import { Experimental_StdioMCPTransport } from 'ai/mcp-stdio';
349
+ import { experimental_createMCPClient } from "ai";
350
+ import { Experimental_StdioMCPTransport } from "ai/mcp-stdio";
218
351
  ```
219
352
 
220
353
  with
221
354
 
222
355
  ```ts
223
- import { experimental_createMCPClient } from '@ai-sdk/mcp';
224
- import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio';
356
+ import { experimental_createMCPClient } from "@ai-sdk/mcp";
357
+ import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
225
358
  ```
226
359
 
227
360
  ### Patch Changes
@@ -587,13 +720,13 @@
587
720
  This change replaces
588
721
 
589
722
  ```ts
590
- import { experimental_createMCPClient } from 'ai';
591
- import { Experimental_StdioMCPTransport } from 'ai/mcp-stdio';
723
+ import { experimental_createMCPClient } from "ai";
724
+ import { Experimental_StdioMCPTransport } from "ai/mcp-stdio";
592
725
  ```
593
726
 
594
727
  with
595
728
 
596
729
  ```ts
597
- import { experimental_createMCPClient } from '@ai-sdk/mcp';
598
- import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio';
730
+ import { experimental_createMCPClient } from "@ai-sdk/mcp";
731
+ import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
599
732
  ```
package/dist/index.d.mts CHANGED
@@ -182,6 +182,8 @@ interface OAuthClientProvider {
182
182
  clientInformation(): OAuthClientInformation | undefined | Promise<OAuthClientInformation | undefined>;
183
183
  saveClientInformation?(clientInformation: OAuthClientInformation): void | Promise<void>;
184
184
  state?(): string | Promise<string>;
185
+ saveState?(state: string): void | Promise<void>;
186
+ storedState?(): string | undefined | Promise<string | undefined>;
185
187
  validateResourceURL?(serverUrl: string | URL, resource?: string): Promise<URL | undefined>;
186
188
  }
187
189
  declare class UnauthorizedError extends Error {
@@ -190,6 +192,7 @@ declare class UnauthorizedError extends Error {
190
192
  declare function auth(provider: OAuthClientProvider, options: {
191
193
  serverUrl: string | URL;
192
194
  authorizationCode?: string;
195
+ callbackState?: string;
193
196
  scope?: string;
194
197
  resourceMetadataUrl?: URL;
195
198
  fetchFn?: FetchFunction;
@@ -240,6 +243,19 @@ type MCPTransportConfig = {
240
243
  * An optional OAuth client provider to use for authentication for MCP servers.
241
244
  */
242
245
  authProvider?: OAuthClientProvider;
246
+ /**
247
+ * Controls how HTTP redirects are handled for transport requests.
248
+ * - `'follow'`: Follow redirects automatically (standard fetch behavior).
249
+ * - `'error'`: Reject any redirect response with an error.
250
+ * @default 'error'
251
+ */
252
+ redirect?: 'follow' | 'error';
253
+ /**
254
+ * Optional custom fetch implementation to use for HTTP requests.
255
+ * Useful for runtimes that need a request-local fetch.
256
+ * @default globalThis.fetch
257
+ */
258
+ fetch?: FetchFunction;
243
259
  };
244
260
 
245
261
  /** MCP tool metadata - keys should follow MCP _meta key format specification */
package/dist/index.d.ts CHANGED
@@ -182,6 +182,8 @@ interface OAuthClientProvider {
182
182
  clientInformation(): OAuthClientInformation | undefined | Promise<OAuthClientInformation | undefined>;
183
183
  saveClientInformation?(clientInformation: OAuthClientInformation): void | Promise<void>;
184
184
  state?(): string | Promise<string>;
185
+ saveState?(state: string): void | Promise<void>;
186
+ storedState?(): string | undefined | Promise<string | undefined>;
185
187
  validateResourceURL?(serverUrl: string | URL, resource?: string): Promise<URL | undefined>;
186
188
  }
187
189
  declare class UnauthorizedError extends Error {
@@ -190,6 +192,7 @@ declare class UnauthorizedError extends Error {
190
192
  declare function auth(provider: OAuthClientProvider, options: {
191
193
  serverUrl: string | URL;
192
194
  authorizationCode?: string;
195
+ callbackState?: string;
193
196
  scope?: string;
194
197
  resourceMetadataUrl?: URL;
195
198
  fetchFn?: FetchFunction;
@@ -240,6 +243,19 @@ type MCPTransportConfig = {
240
243
  * An optional OAuth client provider to use for authentication for MCP servers.
241
244
  */
242
245
  authProvider?: OAuthClientProvider;
246
+ /**
247
+ * Controls how HTTP redirects are handled for transport requests.
248
+ * - `'follow'`: Follow redirects automatically (standard fetch behavior).
249
+ * - `'error'`: Reject any redirect response with an error.
250
+ * @default 'error'
251
+ */
252
+ redirect?: 'follow' | 'error';
253
+ /**
254
+ * Optional custom fetch implementation to use for HTTP requests.
255
+ * Useful for runtimes that need a request-local fetch.
256
+ * @default globalThis.fetch
257
+ */
258
+ fetch?: FetchFunction;
243
259
  };
244
260
 
245
261
  /** MCP tool metadata - keys should follow MCP _meta key format specification */
package/dist/index.js CHANGED
@@ -28,8 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
33
  ElicitResultSchema: () => ElicitResultSchema,
34
34
  ElicitationRequestSchema: () => ElicitationRequestSchema,
35
35
  UnauthorizedError: () => UnauthorizedError,
@@ -37,7 +37,7 @@ __export(src_exports, {
37
37
  createMCPClient: () => createMCPClient,
38
38
  experimental_createMCPClient: () => createMCPClient
39
39
  });
40
- module.exports = __toCommonJS(src_exports);
40
+ module.exports = __toCommonJS(index_exports);
41
41
 
42
42
  // src/tool/mcp-client.ts
43
43
  var import_provider_utils3 = require("@ai-sdk/provider-utils");
@@ -468,6 +468,13 @@ function resourceUrlFromServerUrl(url) {
468
468
  resourceURL.hash = "";
469
469
  return resourceURL;
470
470
  }
471
+ function resourceUrlStripSlash(resource) {
472
+ const href = resource.href;
473
+ if (resource.pathname === "/" && href.endsWith("/")) {
474
+ return href.slice(0, -1);
475
+ }
476
+ return href;
477
+ }
471
478
  function checkResourceAllowed({
472
479
  requestedResource,
473
480
  configuredResource
@@ -706,7 +713,10 @@ async function startAuthorization(authorizationServerUrl, {
706
713
  authorizationUrl.searchParams.append("prompt", "consent");
707
714
  }
708
715
  if (resource) {
709
- authorizationUrl.searchParams.set("resource", resource.href);
716
+ authorizationUrl.searchParams.set(
717
+ "resource",
718
+ resourceUrlStripSlash(resource)
719
+ );
710
720
  }
711
721
  return { authorizationUrl, codeVerifier };
712
722
  }
@@ -815,7 +825,7 @@ async function exchangeAuthorization(authorizationServerUrl, {
815
825
  applyClientAuthentication(authMethod, clientInformation, headers, params);
816
826
  }
817
827
  if (resource) {
818
- params.set("resource", resource.href);
828
+ params.set("resource", resourceUrlStripSlash(resource));
819
829
  }
820
830
  const response = await (fetchFn != null ? fetchFn : fetch)(tokenUrl, {
821
831
  method: "POST",
@@ -867,7 +877,7 @@ async function refreshAuthorization(authorizationServerUrl, {
867
877
  applyClientAuthentication(authMethod, clientInformation, headers, params);
868
878
  }
869
879
  if (resource) {
870
- params.set("resource", resource.href);
880
+ params.set("resource", resourceUrlStripSlash(resource));
871
881
  }
872
882
  const response = await (fetchFn != null ? fetchFn : fetch)(tokenUrl, {
873
883
  method: "POST",
@@ -949,6 +959,7 @@ async function selectResourceURL(serverUrl, provider, resourceMetadata) {
949
959
  async function authInternal(provider, {
950
960
  serverUrl,
951
961
  authorizationCode,
962
+ callbackState,
952
963
  scope,
953
964
  resourceMetadataUrl,
954
965
  fetchFn
@@ -1001,6 +1012,14 @@ async function authInternal(provider, {
1001
1012
  clientInformation = fullInformation;
1002
1013
  }
1003
1014
  if (authorizationCode !== void 0) {
1015
+ if (provider.storedState) {
1016
+ const expectedState = await provider.storedState();
1017
+ if (expectedState !== void 0 && expectedState !== callbackState) {
1018
+ throw new Error(
1019
+ "OAuth state parameter mismatch - possible CSRF attack"
1020
+ );
1021
+ }
1022
+ }
1004
1023
  const codeVerifier2 = await provider.codeVerifier();
1005
1024
  const tokens2 = await exchangeAuthorization(authorizationServerUrl, {
1006
1025
  metadata,
@@ -1039,6 +1058,9 @@ async function authInternal(provider, {
1039
1058
  }
1040
1059
  }
1041
1060
  const state = provider.state ? await provider.state() : void 0;
1061
+ if (state && provider.saveState) {
1062
+ await provider.saveState(state);
1063
+ }
1042
1064
  const { authorizationUrl, codeVerifier } = await startAuthorization(
1043
1065
  authorizationServerUrl,
1044
1066
  {
@@ -1060,12 +1082,16 @@ var SseMCPTransport = class {
1060
1082
  constructor({
1061
1083
  url,
1062
1084
  headers,
1063
- authProvider
1085
+ authProvider,
1086
+ redirect = "error",
1087
+ fetch: fetchFn
1064
1088
  }) {
1065
1089
  this.connected = false;
1066
1090
  this.url = new URL(url);
1067
1091
  this.headers = headers;
1068
1092
  this.authProvider = authProvider;
1093
+ this.redirectMode = redirect;
1094
+ this.fetchFn = fetchFn != null ? fetchFn : globalThis.fetch;
1069
1095
  }
1070
1096
  async commonHeaders(base) {
1071
1097
  const headers = {
@@ -1097,16 +1123,18 @@ var SseMCPTransport = class {
1097
1123
  const headers = await this.commonHeaders({
1098
1124
  Accept: "text/event-stream"
1099
1125
  });
1100
- const response = await fetch(this.url.href, {
1126
+ const response = await this.fetchFn(this.url.href, {
1101
1127
  headers,
1102
- signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal
1128
+ signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal,
1129
+ redirect: this.redirectMode
1103
1130
  });
1104
1131
  if (response.status === 401 && this.authProvider && !triedAuth) {
1105
1132
  this.resourceMetadataUrl = extractResourceMetadataUrl(response);
1106
1133
  try {
1107
1134
  const result = await auth(this.authProvider, {
1108
1135
  serverUrl: this.url,
1109
- resourceMetadataUrl: this.resourceMetadataUrl
1136
+ resourceMetadataUrl: this.resourceMetadataUrl,
1137
+ fetchFn: this.fetchFn
1110
1138
  });
1111
1139
  if (result !== "AUTHORIZED") {
1112
1140
  const error = new UnauthorizedError();
@@ -1218,15 +1246,17 @@ var SseMCPTransport = class {
1218
1246
  method: "POST",
1219
1247
  headers,
1220
1248
  body: JSON.stringify(message),
1221
- signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal
1249
+ signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal,
1250
+ redirect: this.redirectMode
1222
1251
  };
1223
- const response = await fetch(endpoint, init);
1252
+ const response = await this.fetchFn(endpoint.href, init);
1224
1253
  if (response.status === 401 && this.authProvider && !triedAuth) {
1225
1254
  this.resourceMetadataUrl = extractResourceMetadataUrl(response);
1226
1255
  try {
1227
1256
  const result = await auth(this.authProvider, {
1228
1257
  serverUrl: this.url,
1229
- resourceMetadataUrl: this.resourceMetadataUrl
1258
+ resourceMetadataUrl: this.resourceMetadataUrl,
1259
+ fetchFn: this.fetchFn
1230
1260
  });
1231
1261
  if (result !== "AUTHORIZED") {
1232
1262
  const error = new UnauthorizedError();
@@ -1262,7 +1292,9 @@ var HttpMCPTransport = class {
1262
1292
  constructor({
1263
1293
  url,
1264
1294
  headers,
1265
- authProvider
1295
+ authProvider,
1296
+ redirect = "error",
1297
+ fetch: fetchFn
1266
1298
  }) {
1267
1299
  this.inboundReconnectAttempts = 0;
1268
1300
  this.reconnectionOptions = {
@@ -1274,6 +1306,8 @@ var HttpMCPTransport = class {
1274
1306
  this.url = new URL(url);
1275
1307
  this.headers = headers;
1276
1308
  this.authProvider = authProvider;
1309
+ this.redirectMode = redirect;
1310
+ this.fetchFn = fetchFn != null ? fetchFn : globalThis.fetch;
1277
1311
  }
1278
1312
  async commonHeaders(base) {
1279
1313
  const headers = {
@@ -1311,10 +1345,11 @@ var HttpMCPTransport = class {
1311
1345
  try {
1312
1346
  if (this.sessionId && this.abortController && !this.abortController.signal.aborted) {
1313
1347
  const headers = await this.commonHeaders({});
1314
- await fetch(this.url, {
1348
+ await this.fetchFn(this.url.href, {
1315
1349
  method: "DELETE",
1316
1350
  headers,
1317
- signal: this.abortController.signal
1351
+ signal: this.abortController.signal,
1352
+ redirect: this.redirectMode
1318
1353
  }).catch(() => void 0);
1319
1354
  }
1320
1355
  } catch (e) {
@@ -1334,9 +1369,10 @@ var HttpMCPTransport = class {
1334
1369
  method: "POST",
1335
1370
  headers,
1336
1371
  body: JSON.stringify(message),
1337
- signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal
1372
+ signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal,
1373
+ redirect: this.redirectMode
1338
1374
  };
1339
- const response = await fetch(this.url, init);
1375
+ const response = await this.fetchFn(this.url.href, init);
1340
1376
  const sessionId = response.headers.get("mcp-session-id");
1341
1377
  if (sessionId) {
1342
1378
  this.sessionId = sessionId;
@@ -1346,7 +1382,8 @@ var HttpMCPTransport = class {
1346
1382
  try {
1347
1383
  const result = await auth(this.authProvider, {
1348
1384
  serverUrl: this.url,
1349
- resourceMetadataUrl: this.resourceMetadataUrl
1385
+ resourceMetadataUrl: this.resourceMetadataUrl,
1386
+ fetchFn: this.fetchFn
1350
1387
  });
1351
1388
  if (result !== "AUTHORIZED") {
1352
1389
  const error2 = new UnauthorizedError();
@@ -1480,10 +1517,11 @@ var HttpMCPTransport = class {
1480
1517
  if (resumeToken) {
1481
1518
  headers["last-event-id"] = resumeToken;
1482
1519
  }
1483
- const response = await fetch(this.url.href, {
1520
+ const response = await this.fetchFn(this.url.href, {
1484
1521
  method: "GET",
1485
1522
  headers,
1486
- signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal
1523
+ signal: (_a3 = this.abortController) == null ? void 0 : _a3.signal,
1524
+ redirect: this.redirectMode
1487
1525
  });
1488
1526
  const sessionId = response.headers.get("mcp-session-id");
1489
1527
  if (sessionId) {
@@ -1494,7 +1532,8 @@ var HttpMCPTransport = class {
1494
1532
  try {
1495
1533
  const result = await auth(this.authProvider, {
1496
1534
  serverUrl: this.url,
1497
- resourceMetadataUrl: this.resourceMetadataUrl
1535
+ resourceMetadataUrl: this.resourceMetadataUrl,
1536
+ fetchFn: this.fetchFn
1498
1537
  });
1499
1538
  if (result !== "AUTHORIZED") {
1500
1539
  const error = new UnauthorizedError();
@@ -1929,6 +1968,9 @@ var DefaultMCPClient = class {
1929
1968
  var _a4;
1930
1969
  (_a4 = options == null ? void 0 : options.abortSignal) == null ? void 0 : _a4.throwIfAborted();
1931
1970
  const result = await self.callTool({ name: name3, args, options });
1971
+ if (result.isError) {
1972
+ return result;
1973
+ }
1932
1974
  if (outputSchema != null) {
1933
1975
  return self.extractStructuredContent(result, outputSchema, name3);
1934
1976
  }