@ai-sdk/mcp 1.0.56 → 1.0.57

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/mcp",
3
- "version": "1.0.56",
3
+ "version": "1.0.57",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -33,8 +33,8 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "pkce-challenge": "^5.0.0",
36
- "@ai-sdk/provider": "3.0.13",
37
- "@ai-sdk/provider-utils": "4.0.34"
36
+ "@ai-sdk/provider-utils": "4.0.35",
37
+ "@ai-sdk/provider": "3.0.13"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "20.17.24",
@@ -3,6 +3,7 @@ import {
3
3
  asSchema,
4
4
  dynamicTool,
5
5
  jsonSchema,
6
+ retryWithExponentialBackoff,
6
7
  safeParseJSON,
7
8
  safeValidateTypes,
8
9
  tool,
@@ -64,6 +65,82 @@ import {
64
65
  } from './types';
65
66
 
66
67
  const CLIENT_VERSION = '1.0.0';
68
+ const DEFAULT_MAX_TOOL_CALL_RETRIES = 0;
69
+
70
+ const DEFAULT_RETRY_ERROR_CODES = [
71
+ 'ConnectionRefused',
72
+ 'ConnectionClosed',
73
+ 'FailedToOpenSocket',
74
+ 'ECONNRESET',
75
+ 'ECONNREFUSED',
76
+ 'ETIMEDOUT',
77
+ 'EPIPE',
78
+ ];
79
+
80
+ function getErrorStatusCode(error: unknown): number | undefined {
81
+ if (
82
+ error != null &&
83
+ typeof error === 'object' &&
84
+ 'statusCode' in error &&
85
+ typeof error.statusCode === 'number'
86
+ ) {
87
+ return error.statusCode;
88
+ }
89
+
90
+ return undefined;
91
+ }
92
+
93
+ function getStringErrorCode(error: unknown): string | undefined {
94
+ if (
95
+ error != null &&
96
+ typeof error === 'object' &&
97
+ 'code' in error &&
98
+ typeof error.code === 'string'
99
+ ) {
100
+ return error.code;
101
+ }
102
+
103
+ return undefined;
104
+ }
105
+
106
+ function isRetryableMCPToolCallError(error: unknown): boolean {
107
+ const statusCode = getErrorStatusCode(error);
108
+ if (statusCode != null) {
109
+ return (
110
+ statusCode === 408 ||
111
+ statusCode === 409 ||
112
+ statusCode === 429 ||
113
+ statusCode >= 500
114
+ );
115
+ }
116
+
117
+ if (MCPClientError.isInstance(error) && error.code != null) {
118
+ return false;
119
+ }
120
+
121
+ const errorCode = getStringErrorCode(error);
122
+ return errorCode != null && DEFAULT_RETRY_ERROR_CODES.includes(errorCode);
123
+ }
124
+
125
+ function prepareMaxRetries(maxRetries: number | undefined): number {
126
+ if (maxRetries == null) {
127
+ return DEFAULT_MAX_TOOL_CALL_RETRIES;
128
+ }
129
+
130
+ if (!Number.isInteger(maxRetries)) {
131
+ throw new MCPClientError({
132
+ message: 'maxRetries must be an integer',
133
+ });
134
+ }
135
+
136
+ if (maxRetries < 0) {
137
+ throw new MCPClientError({
138
+ message: 'maxRetries must be >= 0',
139
+ });
140
+ }
141
+
142
+ return maxRetries;
143
+ }
67
144
 
68
145
  function mcpToModelOutput({
69
146
  output,
@@ -102,6 +179,15 @@ export interface MCPClientConfig {
102
179
  transport: MCPTransportConfig | MCPTransport;
103
180
  /** Optional callback for uncaught errors */
104
181
  onUncaughtError?: (error: unknown) => void;
182
+ /**
183
+ * Maximum number of retries for transient MCP tool call failures.
184
+ *
185
+ * Set to 0 to disable retries. Retries only apply to tools/call requests.
186
+ * JSON-RPC application errors, such as invalid params, are not retried.
187
+ *
188
+ * @default 0
189
+ */
190
+ maxRetries?: number;
105
191
  /** Optional client name, defaults to 'ai-sdk-mcp-client' */
106
192
  clientName?: string;
107
193
  /**
@@ -225,6 +311,7 @@ export interface MCPClient {
225
311
  class DefaultMCPClient implements MCPClient {
226
312
  private transport: MCPTransport;
227
313
  private onUncaughtError?: (error: unknown) => void;
314
+ private maxRetries: number;
228
315
  private clientInfo: ClientConfiguration;
229
316
  private clientCapabilities: ClientCapabilities;
230
317
  private requestMessageId = 0;
@@ -246,9 +333,11 @@ class DefaultMCPClient implements MCPClient {
246
333
  clientName = name ?? 'ai-sdk-mcp-client',
247
334
  version = CLIENT_VERSION,
248
335
  onUncaughtError,
336
+ maxRetries,
249
337
  capabilities,
250
338
  }: MCPClientConfig) {
251
339
  this.onUncaughtError = onUncaughtError;
340
+ this.maxRetries = prepareMaxRetries(maxRetries);
252
341
  this.clientCapabilities = capabilities ?? {};
253
342
 
254
343
  if (isCustomMcpTransport(transportConfig)) {
@@ -470,7 +559,30 @@ class DefaultMCPClient implements MCPClient {
470
559
  });
471
560
  }
472
561
 
473
- private async callTool({
562
+ private async callToolWithRetry({
563
+ options,
564
+ execute,
565
+ }: {
566
+ options?: ToolExecutionOptions;
567
+ execute: () => Promise<CallToolResult>;
568
+ }): Promise<CallToolResult> {
569
+ if (this.maxRetries === 0) {
570
+ return execute();
571
+ }
572
+
573
+ return retryWithExponentialBackoff({
574
+ maxRetries: this.maxRetries,
575
+ abortSignal: options?.abortSignal,
576
+ shouldRetry: isRetryableMCPToolCallError,
577
+ createRetryError: ({ message, errors }) =>
578
+ new MCPClientError({
579
+ message,
580
+ cause: errors[errors.length - 1],
581
+ }),
582
+ })(execute);
583
+ }
584
+
585
+ async callTool({
474
586
  name,
475
587
  args,
476
588
  options,
@@ -480,12 +592,19 @@ class DefaultMCPClient implements MCPClient {
480
592
  options?: ToolExecutionOptions;
481
593
  }): Promise<CallToolResult> {
482
594
  try {
483
- return this.request({
484
- request: { method: 'tools/call', params: { name, arguments: args } },
485
- resultSchema: CallToolResultSchema,
486
- options: {
487
- signal: options?.abortSignal,
488
- },
595
+ return this.callToolWithRetry({
596
+ options,
597
+ execute: () =>
598
+ this.request({
599
+ request: {
600
+ method: 'tools/call',
601
+ params: { name, arguments: args },
602
+ },
603
+ resultSchema: CallToolResultSchema,
604
+ options: {
605
+ signal: options?.abortSignal,
606
+ },
607
+ }),
489
608
  });
490
609
  } catch (error) {
491
610
  throw error;