@lobehub/lobehub 2.0.0-next.150 → 2.0.0-next.151

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.
@@ -399,6 +399,13 @@
399
399
  "when": 1764685643024,
400
400
  "tag": "0056_update_agent_slug_index",
401
401
  "breakpoints": true
402
+ },
403
+ {
404
+ "idx": 57,
405
+ "version": "7",
406
+ "when": 1764734167674,
407
+ "tag": "0057_add_topic_user_memory_extract_status",
408
+ "breakpoints": true
402
409
  }
403
410
  ],
404
411
  "version": "6"
@@ -223,7 +223,10 @@
223
223
  "hash": "9646161fa041354714f823d726af27247bcd6e60fa3be5698c0d69f337a5700b"
224
224
  },
225
225
  {
226
- "sql": ["DROP TABLE \"user_budgets\";", "\nDROP TABLE \"user_subscriptions\";"],
226
+ "sql": [
227
+ "DROP TABLE \"user_budgets\";",
228
+ "\nDROP TABLE \"user_subscriptions\";"
229
+ ],
227
230
  "bps": true,
228
231
  "folderMillis": 1729699958471,
229
232
  "hash": "7dad43a2a25d1aec82124a4e53f8d82f8505c3073f23606c1dc5d2a4598eacf9"
@@ -295,7 +298,9 @@
295
298
  "hash": "845a692ceabbfc3caf252a97d3e19a213bc0c433df2689900135f9cfded2cf49"
296
299
  },
297
300
  {
298
- "sql": ["ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"],
301
+ "sql": [
302
+ "ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"
303
+ ],
299
304
  "bps": true,
300
305
  "folderMillis": 1737609172353,
301
306
  "hash": "2cb36ae4fcdd7b7064767e04bfbb36ae34518ff4bb1b39006f2dd394d1893868"
@@ -510,7 +515,9 @@
510
515
  "hash": "a7ccf007fd185ff922823148d1eae6fafe652fc98d2fd2793f84a84f29e93cd1"
511
516
  },
512
517
  {
513
- "sql": ["ALTER TABLE \"ai_providers\" ADD COLUMN \"config\" jsonb;"],
518
+ "sql": [
519
+ "ALTER TABLE \"ai_providers\" ADD COLUMN \"config\" jsonb;"
520
+ ],
514
521
  "bps": true,
515
522
  "folderMillis": 1749309388370,
516
523
  "hash": "39cea379f08ee4cb944875c0b67f7791387b508c2d47958bb4cd501ed1ef33eb"
@@ -628,7 +635,9 @@
628
635
  "hash": "1ba9b1f74ea13348da98d6fcdad7867ab4316ed565bf75d84d160c526cdac14b"
629
636
  },
630
637
  {
631
- "sql": ["ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"],
638
+ "sql": [
639
+ "ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"
640
+ ],
632
641
  "bps": true,
633
642
  "folderMillis": 1759116400580,
634
643
  "hash": "433ddae88e785f2db734e49a4c115eee93e60afe389f7919d66e5ba9aa159a37"
@@ -678,13 +687,17 @@
678
687
  "hash": "4bdc6505797d7a33b622498c138cfd47f637239f6905e1c484cd01d9d5f21d6b"
679
688
  },
680
689
  {
681
- "sql": ["ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"],
690
+ "sql": [
691
+ "ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"
692
+ ],
682
693
  "bps": true,
683
694
  "folderMillis": 1760108430562,
684
695
  "hash": "ce09b301abb80f6563abc2f526bdd20b4f69bae430f09ba2179b9e3bfec43067"
685
696
  },
686
697
  {
687
- "sql": ["ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"],
698
+ "sql": [
699
+ "ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
700
+ ],
688
701
  "bps": true,
689
702
  "folderMillis": 1761554153406,
690
703
  "hash": "bf2f21293e90e11cf60a784cf3ec219eafa95f7545d7d2f9d1449c0b0949599a"
@@ -764,13 +777,17 @@
764
777
  "hash": "923ccbdf46c32be9a981dabd348e6923b4a365444241e9b8cc174bf5b914cbc5"
765
778
  },
766
779
  {
767
- "sql": ["ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"market_identifier\" text;\n"],
780
+ "sql": [
781
+ "ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"market_identifier\" text;\n"
782
+ ],
768
783
  "bps": true,
769
784
  "folderMillis": 1762870034882,
770
785
  "hash": "4178aacb4b8892b7fd15d29209bbf9b1d1f9d7c406ba796f27542c0bcd919680"
771
786
  },
772
787
  {
773
- "sql": ["ALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"intervention\" jsonb;\n"],
788
+ "sql": [
789
+ "ALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"intervention\" jsonb;\n"
790
+ ],
774
791
  "bps": true,
775
792
  "folderMillis": 1762911968658,
776
793
  "hash": "552a032cc0e595277232e70b5f9338658585bafe9481ae8346a5f322b673a68b"
@@ -799,7 +816,9 @@
799
816
  "hash": "f823b521f4d25e5dc5ab238b372727d2d2d7f0aed27b5eabc8a9608ce4e50568"
800
817
  },
801
818
  {
802
- "sql": ["ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"],
819
+ "sql": [
820
+ "ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
821
+ ],
803
822
  "bps": true,
804
823
  "folderMillis": 1764215503726,
805
824
  "hash": "4188893a9083b3c7baebdbad0dd3f9d9400ede7584ca2394f5c64305dc9ec7b0"
@@ -840,7 +859,9 @@
840
859
  "hash": "2c103eee82bdf329944fb622dd9c2b9f20df80eb54f23eb9254d2285de413099"
841
860
  },
842
861
  {
843
- "sql": ["ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"market\" jsonb;"],
862
+ "sql": [
863
+ "ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"market\" jsonb;"
864
+ ],
844
865
  "bps": true,
845
866
  "folderMillis": 1764335703306,
846
867
  "hash": "28c0d738c0b1fdf5fd871363be1a1477b4accbabdc140fe8dc6e9b339aae2c89"
@@ -910,5 +931,13 @@
910
931
  "bps": true,
911
932
  "folderMillis": 1764685643024,
912
933
  "hash": "6e7ac7f964eb03efa3cb0d2fd35ded23e25c3abf955c4c2a51418f8daef54af9"
934
+ },
935
+ {
936
+ "sql": [
937
+ "CREATE INDEX IF NOT EXISTS \"topics_extract_status_gin_idx\" ON \"topics\" USING gin ((metadata->'userMemoryExtractStatus') jsonb_path_ops);\n"
938
+ ],
939
+ "bps": true,
940
+ "folderMillis": 1764734167674,
941
+ "hash": "89c134be2948d3afc360d6bac11dea0c6fd5c902bf6093ed077033adb920fd02"
913
942
  }
914
- ]
943
+ ]
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
2
  import type { ChatTopicMetadata } from '@lobechat/types';
3
+ import { sql } from 'drizzle-orm';
3
4
  import { boolean, index, jsonb, pgTable, primaryKey, text, uniqueIndex } from 'drizzle-orm/pg-core';
4
5
  import { createInsertSchema } from 'drizzle-zod';
5
6
 
@@ -39,6 +40,10 @@ export const topics = pgTable(
39
40
  index('topics_session_id_idx').on(t.sessionId),
40
41
  index('topics_group_id_idx').on(t.groupId),
41
42
  index('topics_agent_id_idx').on(t.agentId),
43
+ index('topics_extract_status_gin_idx').using(
44
+ 'gin',
45
+ sql`(metadata->'userMemoryExtractStatus') jsonb_path_ops`,
46
+ ),
42
47
  ],
43
48
  );
44
49
 
@@ -17,6 +17,7 @@
17
17
  "@lobechat/const": "workspace:*",
18
18
  "@lobechat/types": "workspace:*",
19
19
  "@lobechat/utils": "workspace:*",
20
+ "async-retry": "^1.3.3",
20
21
  "debug": "^4.4.3",
21
22
  "model-bank": "workspace:*",
22
23
  "openai": "^4.104.0"
@@ -1,3 +1,5 @@
1
+ import retry from 'async-retry';
2
+
1
3
  export interface TaskResult<T> {
2
4
  data?: T;
3
5
  error?: any;
@@ -23,14 +25,14 @@ export interface AsyncifyPollingOptions<T, R> {
23
25
  checkStatus: (result: T) => TaskResult<R>;
24
26
 
25
27
  // Retry configuration
26
- initialInterval?: number;
28
+ initialInterval?: number;
27
29
  // Optional logger
28
30
  logger?: {
29
31
  debug?: (...args: any[]) => void;
30
32
  error?: (...args: any[]) => void;
31
- };
33
+ };
32
34
  // Default 1.5
33
- maxConsecutiveFailures?: number;
35
+ maxConsecutiveFailures?: number;
34
36
  // Default 500ms
35
37
  maxInterval?: number; // Default 3
36
38
  maxRetries?: number; // Default Infinity
@@ -42,6 +44,24 @@ export interface AsyncifyPollingOptions<T, R> {
42
44
  pollingQuery: () => Promise<T>;
43
45
  }
44
46
 
47
+ // Internal error class to signal that polling should continue
48
+ class PendingError extends Error {
49
+ constructor() {
50
+ super('Task is pending, continue polling');
51
+ this.name = 'PendingError';
52
+ }
53
+ }
54
+
55
+ // Internal error class to signal that task has failed and should not retry
56
+ class TaskFailedError extends Error {
57
+ originalError: any;
58
+ constructor(error: any) {
59
+ super(error instanceof Error ? error.message : String(error));
60
+ this.name = 'TaskFailedError';
61
+ this.originalError = error;
62
+ }
63
+ }
64
+
45
65
  /**
46
66
  * Convert polling pattern to async/await pattern
47
67
  *
@@ -62,114 +82,117 @@ export async function asyncifyPolling<T, R>(options: AsyncifyPollingOptions<T, R
62
82
  logger,
63
83
  } = options;
64
84
 
65
- let retries = 0;
66
85
  let consecutiveFailures = 0;
67
86
 
68
- while (retries < maxRetries) {
69
- let pollingResult: T;
70
-
71
- try {
72
- // Execute polling function
73
- pollingResult = await pollingQuery();
74
-
75
- // Reset consecutive failures counter on successful execution
76
- consecutiveFailures = 0;
77
- } catch (error) {
78
- // Polling function execution failed (network error, etc.)
79
- consecutiveFailures++;
80
-
81
- logger?.error?.(
82
- `Failed to execute polling function (attempt ${retries + 1}/${maxRetries === Infinity ? '∞' : maxRetries}, consecutive failures: ${consecutiveFailures}/${maxConsecutiveFailures}):`,
83
- error,
84
- );
85
-
86
- // Handle custom error processing if provided
87
- if (onPollingError) {
88
- const errorResult = onPollingError({
89
- consecutiveFailures,
90
- error,
91
- retries,
92
- });
93
-
94
- if (!errorResult.isContinuePolling) {
95
- // Custom error handler decided to stop polling
96
- throw errorResult.error || error;
97
- }
98
-
99
- // Custom error handler decided to continue polling
100
- logger?.debug?.('Custom error handler decided to continue polling');
101
- } else {
102
- // Default behavior: check if maximum consecutive failures reached
103
- if (consecutiveFailures >= maxConsecutiveFailures) {
104
- throw new Error(
105
- `Failed to execute polling function after ${consecutiveFailures} consecutive attempts: ${error}`,
87
+ // async-retry uses Infinity for retries when maxRetries is Infinity
88
+ // but we need to handle this case properly
89
+ const retriesConfig = maxRetries === Infinity ? 1_000_000 : maxRetries - 1;
90
+
91
+ try {
92
+ return await retry(
93
+ async (bail, attemptNumber) => {
94
+ const retries = attemptNumber - 1;
95
+
96
+ try {
97
+ // Execute polling function
98
+ const pollingResult = await pollingQuery();
99
+
100
+ // Reset consecutive failures counter on successful execution
101
+ consecutiveFailures = 0;
102
+
103
+ // Check task status
104
+ const statusResult = checkStatus(pollingResult);
105
+
106
+ logger?.debug?.(`Task status: ${statusResult.status} (attempt ${attemptNumber})`);
107
+
108
+ switch (statusResult.status) {
109
+ case 'success': {
110
+ return statusResult.data as R;
111
+ }
112
+
113
+ case 'failed': {
114
+ // Task logic failed, throw error immediately (not counted as consecutive failure)
115
+ bail(new TaskFailedError(statusResult.error || new Error('Task failed')));
116
+ // This return is never reached due to bail, but needed for type safety
117
+ return undefined as R;
118
+ }
119
+
120
+ default: {
121
+ // 'pending' or unknown status - continue polling by throwing PendingError
122
+ throw new PendingError();
123
+ }
124
+ }
125
+ } catch (error) {
126
+ // Re-throw internal errors that should be handled by async-retry
127
+ if (error instanceof PendingError) {
128
+ throw error;
129
+ }
130
+
131
+ // Polling function execution failed (network error, etc.)
132
+ consecutiveFailures++;
133
+
134
+ logger?.error?.(
135
+ `Failed to execute polling function (attempt ${attemptNumber}/${maxRetries === Infinity ? '∞' : maxRetries}, consecutive failures: ${consecutiveFailures}/${maxConsecutiveFailures}):`,
136
+ error,
106
137
  );
107
- }
108
- }
109
-
110
- // Wait before retry and continue to next loop iteration
111
- if (retries < maxRetries - 1) {
112
- const currentInterval = Math.min(
113
- initialInterval * Math.pow(backoffMultiplier, retries),
114
- maxInterval,
115
- );
116
-
117
- logger?.debug?.(`Waiting ${currentInterval}ms before next retry`);
118
138
 
119
- await new Promise((resolve) => {
120
- setTimeout(resolve, currentInterval);
121
- });
122
- }
123
-
124
- retries++;
125
- continue;
126
- }
127
-
128
- // Check task status
129
- const statusResult = checkStatus(pollingResult);
130
-
131
- logger?.debug?.(`Task status: ${statusResult.status} (attempt ${retries + 1})`);
132
-
133
- switch (statusResult.status) {
134
- case 'success': {
135
- return statusResult.data as R;
136
- }
137
-
138
- case 'failed': {
139
- // Task logic failed, throw error immediately (not counted as consecutive failure)
140
- throw statusResult.error || new Error('Task failed');
141
- }
142
-
143
- case 'pending': {
144
- // Continue polling
145
- break;
146
- }
147
-
148
- default: {
149
- // Unknown status, treat as pending
150
- break;
151
- }
139
+ // Handle custom error processing if provided
140
+ if (onPollingError) {
141
+ const errorResult = onPollingError({
142
+ consecutiveFailures,
143
+ error,
144
+ retries,
145
+ });
146
+
147
+ if (!errorResult.isContinuePolling) {
148
+ // Custom error handler decided to stop polling
149
+ bail(errorResult.error || (error as Error));
150
+ return undefined as R;
151
+ }
152
+
153
+ // Custom error handler decided to continue polling
154
+ logger?.debug?.('Custom error handler decided to continue polling');
155
+ throw error; // Rethrow to trigger retry
156
+ } else {
157
+ // Default behavior: check if maximum consecutive failures reached
158
+ if (consecutiveFailures >= maxConsecutiveFailures) {
159
+ bail(
160
+ new Error(
161
+ `Failed to execute polling function after ${consecutiveFailures} consecutive attempts: ${error}`,
162
+ ),
163
+ );
164
+ return undefined as R;
165
+ }
166
+ }
167
+
168
+ // Rethrow to trigger retry
169
+ throw error;
170
+ }
171
+ },
172
+ {
173
+ factor: backoffMultiplier,
174
+ maxTimeout: maxInterval,
175
+ minTimeout: initialInterval,
176
+ onRetry: (error, attempt) => {
177
+ if (!(error instanceof PendingError)) {
178
+ logger?.debug?.(`Retrying after error (attempt ${attempt})`);
179
+ }
180
+ },
181
+ randomize: false, // Disable jitter for predictable intervals
182
+ retries: retriesConfig,
183
+ },
184
+ );
185
+ } catch (error) {
186
+ // Handle TaskFailedError by throwing the original error
187
+ if (error instanceof TaskFailedError) {
188
+ throw error.originalError;
152
189
  }
153
190
 
154
- // Wait before next retry if not the last attempt
155
- if (retries < maxRetries - 1) {
156
- // Calculate dynamic retry interval with exponential backoff
157
- const currentInterval = Math.min(
158
- initialInterval * Math.pow(backoffMultiplier, retries),
159
- maxInterval,
160
- );
161
-
162
- logger?.debug?.(`Waiting ${currentInterval}ms before next retry`);
163
-
164
- // Wait for retry interval
165
- await new Promise((resolve) => {
166
- setTimeout(resolve, currentInterval);
167
- });
191
+ // Handle max retries exceeded
192
+ if (error instanceof PendingError) {
193
+ throw new Error(`Task timeout after ${maxRetries} attempts`);
168
194
  }
169
195
 
170
- retries++;
196
+ throw error;
171
197
  }
172
-
173
- // Maximum retries reached
174
- throw new Error(`Task timeout after ${maxRetries} attempts`);
175
198
  }
@@ -24,9 +24,21 @@ export interface GroupedTopic {
24
24
  title?: string;
25
25
  }
26
26
 
27
+ export interface TopicUserMemoryExtractRunState {
28
+ error?: string;
29
+ lastConversationDigest?: string;
30
+ lastMessageAt?: string;
31
+ lastRunAt?: string;
32
+ messageCount?: number;
33
+ processedMemoryCount?: number;
34
+ version?: string;
35
+ }
36
+
27
37
  export interface ChatTopicMetadata {
28
38
  model?: string;
29
39
  provider?: string;
40
+ userMemoryExtractRunState?: TopicUserMemoryExtractRunState;
41
+ userMemoryExtractStatus?: 'pending' | 'completed' | 'failed';
30
42
  }
31
43
 
32
44
  export interface ChatTopicSummary {
@@ -324,12 +324,13 @@ describe('MCPService', () => {
324
324
  expect(result).toHaveLength(1);
325
325
  });
326
326
 
327
- it('should throw TRPCError when NoValidSessionId retry exceeds limit', async () => {
327
+ it('should throw original error when NoValidSessionId retry exceeds limit', async () => {
328
328
  // Fail more than 3 times
329
329
  mockClient.listTools.mockRejectedValue(new Error('NoValidSessionId'));
330
330
 
331
- await expect(mcpService.listTools(mockParams)).rejects.toThrow(TRPCError);
332
- expect(mockClient.listTools).toHaveBeenCalledTimes(5); // initial + 4 retry attempts (last one fails condition)
331
+ await expect(mcpService.listTools(mockParams)).rejects.toThrow('NoValidSessionId');
332
+ // async-retry: 1 initial + 3 retries = 4 attempts
333
+ expect(mockClient.listTools).toHaveBeenCalledTimes(4);
333
334
  });
334
335
 
335
336
  it('should throw TRPCError on other errors without retry', async () => {
@@ -340,23 +341,6 @@ describe('MCPService', () => {
340
341
  expect(mockClient.listTools).toHaveBeenCalledTimes(1);
341
342
  });
342
343
 
343
- it('should pass skipCache option to getClient', async () => {
344
- const mockTools = [
345
- {
346
- name: 'tool1',
347
- description: 'Test tool',
348
- inputSchema: { type: 'object' },
349
- },
350
- ];
351
-
352
- mockClient.listTools.mockResolvedValue(mockTools);
353
-
354
- await mcpService.listTools(mockParams, { skipCache: true });
355
-
356
- // Verify getClient was called with skipCache
357
- expect(mcpService.getClient).toHaveBeenCalledWith(mockParams, true);
358
- });
359
-
360
344
  it('should throw TRPCError with correct error message', async () => {
361
345
  const error = new Error('Custom error message');
362
346
  mockClient.listTools.mockRejectedValue(error);
@@ -4,6 +4,7 @@ import { LobeChatPluginApi, LobeChatPluginManifest, PluginSchema } from '@lobehu
4
4
  import { DeploymentOption } from '@lobehub/market-sdk';
5
5
  import { McpError } from '@modelcontextprotocol/sdk/types.js';
6
6
  import { TRPCError } from '@trpc/server';
7
+ import retry from 'async-retry';
7
8
  import debug from 'debug';
8
9
 
9
10
  import {
@@ -36,48 +37,47 @@ export class MCPService {
36
37
  // --- MCP Interaction ---
37
38
 
38
39
  // listTools now accepts MCPClientParams
39
- async listTools(
40
- params: MCPClientParams,
41
- { retryTime, skipCache }: { retryTime?: number; skipCache?: boolean } = {},
42
- ): Promise<LobeChatPluginApi[]> {
43
- const client = await this.getClient(params, skipCache); // Get client using params
40
+ async listTools(params: MCPClientParams): Promise<LobeChatPluginApi[]> {
44
41
  const loggableParams = this.sanitizeForLogging(params);
45
- log(`Listing tools using client for params: %O`, loggableParams);
46
-
47
- try {
48
- const result = await client.listTools();
49
- log(
50
- `Tools listed successfully for params: %O, result count: %d`,
51
- loggableParams,
52
- result.length,
53
- );
54
- return result.map<LobeChatPluginApi>((item) => ({
55
- // Assuming identifier is the unique name/id
56
- description: item.description,
57
- name: item.name,
58
- parameters: item.inputSchema as PluginSchema,
59
- }));
60
- } catch (error) {
61
- let nextReTryTime = retryTime || 0;
62
42
 
63
- if ((error as Error).message === 'NoValidSessionId' && nextReTryTime <= 3) {
64
- if (!nextReTryTime) {
65
- nextReTryTime = 1;
66
- } else {
67
- nextReTryTime += 1;
43
+ return retry(
44
+ async (bail, attemptNumber) => {
45
+ // Skip cache on retry attempts
46
+ const skipCache = attemptNumber > 1;
47
+ const client = await this.getClient(params, skipCache);
48
+ log(`Listing tools using client for params: %O (attempt ${attemptNumber})`, loggableParams);
49
+
50
+ try {
51
+ const result = await client.listTools();
52
+ log(
53
+ `Tools listed successfully for params: %O, result count: %d`,
54
+ loggableParams,
55
+ result.length,
56
+ );
57
+ return result.map<LobeChatPluginApi>((item) => ({
58
+ // Assuming identifier is the unique name/id
59
+ description: item.description,
60
+ name: item.name,
61
+ parameters: item.inputSchema as PluginSchema,
62
+ }));
63
+ } catch (error) {
64
+ // Only retry for NoValidSessionId errors
65
+ if ((error as Error).message !== 'NoValidSessionId') {
66
+ console.error(`Error listing tools for params %O:`, loggableParams, error);
67
+ bail(
68
+ new TRPCError({
69
+ cause: error,
70
+ code: 'INTERNAL_SERVER_ERROR',
71
+ message: `Error listing tools from MCP server: ${(error as Error).message}`,
72
+ }),
73
+ );
74
+ return []; // This line will never be reached due to bail, but needed for type safety
75
+ }
76
+ throw error; // Rethrow to trigger retry
68
77
  }
69
-
70
- return this.listTools(params, { retryTime: nextReTryTime, skipCache: true });
71
- }
72
-
73
- console.error(`Error listing tools for params %O:`, loggableParams, error);
74
- // Propagate a TRPCError for better handling upstream
75
- throw new TRPCError({
76
- cause: error,
77
- code: 'INTERNAL_SERVER_ERROR',
78
- message: `Error listing tools from MCP server: ${(error as Error).message}`,
79
- });
80
- }
78
+ },
79
+ { maxRetryTime: 1000, minTimeout: 100, retries: 3 },
80
+ );
81
81
  }
82
82
 
83
83
  // listTools now accepts MCPClientParams