converse-mcp-server 2.3.1 → 2.4.1

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.
Files changed (42) hide show
  1. package/README.md +771 -738
  2. package/docs/API.md +10 -1
  3. package/docs/PROVIDERS.md +8 -4
  4. package/package.json +12 -12
  5. package/src/async/asyncJobStore.js +82 -52
  6. package/src/async/eventBus.js +25 -20
  7. package/src/async/fileCache.js +121 -40
  8. package/src/async/jobRunner.js +65 -39
  9. package/src/async/providerStreamNormalizer.js +203 -117
  10. package/src/config.js +374 -102
  11. package/src/continuationStore.js +32 -24
  12. package/src/index.js +45 -25
  13. package/src/prompts/helpPrompt.js +328 -305
  14. package/src/providers/anthropic.js +303 -119
  15. package/src/providers/codex.js +103 -45
  16. package/src/providers/deepseek.js +24 -8
  17. package/src/providers/google.js +337 -93
  18. package/src/providers/index.js +1 -1
  19. package/src/providers/interface.js +16 -11
  20. package/src/providers/mistral.js +179 -69
  21. package/src/providers/openai-compatible.js +231 -94
  22. package/src/providers/openai.js +1094 -914
  23. package/src/providers/openrouter-endpoints-client.js +220 -216
  24. package/src/providers/openrouter.js +426 -381
  25. package/src/providers/xai.js +153 -56
  26. package/src/resources/helpResource.js +70 -67
  27. package/src/router.js +95 -67
  28. package/src/services/summarizationService.js +51 -24
  29. package/src/systemPrompts.js +89 -89
  30. package/src/tools/cancelJob.js +31 -19
  31. package/src/tools/chat.js +997 -883
  32. package/src/tools/checkStatus.js +86 -65
  33. package/src/tools/consensus.js +400 -234
  34. package/src/tools/index.js +39 -16
  35. package/src/transport/httpTransport.js +82 -55
  36. package/src/utils/contextProcessor.js +54 -37
  37. package/src/utils/errorHandler.js +95 -45
  38. package/src/utils/fileValidator.js +107 -98
  39. package/src/utils/formatStatus.js +122 -64
  40. package/src/utils/logger.js +459 -449
  41. package/src/utils/pathUtils.js +2 -2
  42. package/src/utils/tokenLimiter.js +216 -216
@@ -61,14 +61,19 @@ export const ERROR_CODES = {
61
61
  INTERNAL_ERROR: 'INTERNAL_ERROR',
62
62
  VALIDATION_ERROR: 'VALIDATION_ERROR',
63
63
  TIMEOUT_ERROR: 'TIMEOUT_ERROR',
64
- NETWORK_ERROR: 'NETWORK_ERROR'
64
+ NETWORK_ERROR: 'NETWORK_ERROR',
65
65
  };
66
66
 
67
67
  /**
68
68
  * Base error class for structured error handling
69
69
  */
70
70
  export class ConverseMCPError extends Error {
71
- constructor(message, code = ERROR_CODES.UNKNOWN_ERROR, details = {}, statusCode = 500) {
71
+ constructor(
72
+ message,
73
+ code = ERROR_CODES.UNKNOWN_ERROR,
74
+ details = {},
75
+ statusCode = 500,
76
+ ) {
72
77
  super(message);
73
78
  this.name = 'ConverseMCPError';
74
79
  this.code = code;
@@ -94,7 +99,7 @@ export class ConverseMCPError extends Error {
94
99
  details: this.details,
95
100
  statusCode: this.statusCode,
96
101
  timestamp: this.timestamp,
97
- stack: this.stack
102
+ stack: this.stack,
98
103
  };
99
104
  }
100
105
 
@@ -107,8 +112,8 @@ export class ConverseMCPError extends Error {
107
112
  content: [
108
113
  {
109
114
  type: 'text',
110
- text: this.message
111
- }
115
+ text: this.message,
116
+ },
112
117
  ],
113
118
  isError: true,
114
119
  error: {
@@ -116,8 +121,8 @@ export class ConverseMCPError extends Error {
116
121
  code: this.code,
117
122
  message: this.message,
118
123
  details: this.details,
119
- timestamp: this.timestamp
120
- }
124
+ timestamp: this.timestamp,
125
+ },
121
126
  };
122
127
  }
123
128
  }
@@ -126,7 +131,12 @@ export class ConverseMCPError extends Error {
126
131
  * Provider-specific error class
127
132
  */
128
133
  export class ProviderError extends ConverseMCPError {
129
- constructor(message, code = ERROR_CODES.PROVIDER_ERROR, details = {}, provider = 'unknown') {
134
+ constructor(
135
+ message,
136
+ code = ERROR_CODES.PROVIDER_ERROR,
137
+ details = {},
138
+ provider = 'unknown',
139
+ ) {
130
140
  super(message, code, { ...details, provider }, 503);
131
141
  this.name = 'ProviderError';
132
142
  this.provider = provider;
@@ -137,7 +147,12 @@ export class ProviderError extends ConverseMCPError {
137
147
  * Tool-specific error class
138
148
  */
139
149
  export class ToolError extends ConverseMCPError {
140
- constructor(message, code = ERROR_CODES.TOOL_ERROR, details = {}, toolName = 'unknown') {
150
+ constructor(
151
+ message,
152
+ code = ERROR_CODES.TOOL_ERROR,
153
+ details = {},
154
+ toolName = 'unknown',
155
+ ) {
141
156
  super(message, code, { ...details, toolName }, 400);
142
157
  this.name = 'ToolError';
143
158
  this.toolName = toolName;
@@ -182,15 +197,20 @@ export class ContextError extends ConverseMCPError {
182
197
  * @param {object} details - Additional details
183
198
  * @returns {ConverseMCPError} Enhanced error
184
199
  */
185
- export function wrapError(originalError, message, code = ERROR_CODES.UNKNOWN_ERROR, details = {}) {
200
+ export function wrapError(
201
+ originalError,
202
+ message,
203
+ code = ERROR_CODES.UNKNOWN_ERROR,
204
+ details = {},
205
+ ) {
186
206
  const enhancedDetails = {
187
207
  ...details,
188
208
  originalError: {
189
209
  name: originalError.name,
190
210
  message: originalError.message,
191
211
  code: originalError.code,
192
- stack: originalError.stack
193
- }
212
+ stack: originalError.stack,
213
+ },
194
214
  };
195
215
 
196
216
  const wrappedError = new ConverseMCPError(message, code, enhancedDetails);
@@ -218,7 +238,7 @@ export function withErrorHandler(fn, operation = 'unknown', context = {}) {
218
238
  } catch (error) {
219
239
  operationLogger.error('Operation failed', {
220
240
  error,
221
- data: { args: args.length, context }
241
+ data: { args: args.length, context },
222
242
  });
223
243
 
224
244
  // Re-throw enhanced error if it's already structured
@@ -227,7 +247,12 @@ export function withErrorHandler(fn, operation = 'unknown', context = {}) {
227
247
  }
228
248
 
229
249
  // Wrap unknown errors
230
- throw wrapError(error, `${operation} failed: ${error.message}`, ERROR_CODES.INTERNAL_ERROR, context);
250
+ throw wrapError(
251
+ error,
252
+ `${operation} failed: ${error.message}`,
253
+ ERROR_CODES.INTERNAL_ERROR,
254
+ context,
255
+ );
231
256
  }
232
257
  };
233
258
  }
@@ -252,14 +277,16 @@ export function createMCPErrorResponse(error, toolName = null, context = {}) {
252
277
 
253
278
  // Create structured response for unknown errors
254
279
  const errorCode = error.code || ERROR_CODES.UNKNOWN_ERROR;
255
- const message = toolName ? `Error in ${toolName}: ${error.message}` : error.message;
280
+ const message = toolName
281
+ ? `Error in ${toolName}: ${error.message}`
282
+ : error.message;
256
283
 
257
284
  return {
258
285
  content: [
259
286
  {
260
287
  type: 'text',
261
- text: message
262
- }
288
+ text: message,
289
+ },
263
290
  ],
264
291
  isError: true,
265
292
  error: {
@@ -269,8 +296,9 @@ export function createMCPErrorResponse(error, toolName = null, context = {}) {
269
296
  toolName,
270
297
  context,
271
298
  timestamp: new Date().toISOString(),
272
- ...(process.env.NODE_ENV === 'development' && error.stack && { stack: error.stack })
273
- }
299
+ ...(process.env.NODE_ENV === 'development' &&
300
+ error.stack && { stack: error.stack }),
301
+ },
274
302
  };
275
303
  }
276
304
 
@@ -290,10 +318,10 @@ export function isRecoverableError(error) {
290
318
  /timeout/i,
291
319
  /rate limit/i,
292
320
  /quota/i,
293
- /temporary/i
321
+ /temporary/i,
294
322
  ];
295
323
 
296
- return recoverablePatterns.some(pattern => pattern.test(error.message));
324
+ return recoverablePatterns.some((pattern) => pattern.test(error.message));
297
325
  }
298
326
 
299
327
  /**
@@ -307,14 +335,20 @@ export function logError(error, operation = 'unknown', metadata = {}) {
307
335
 
308
336
  if (error instanceof ConverseMCPError) {
309
337
  if (error.statusCode >= 500) {
310
- operationLogger.error('Internal error occurred', { error, data: metadata });
338
+ operationLogger.error('Internal error occurred', {
339
+ error,
340
+ data: metadata,
341
+ });
311
342
  } else if (error.statusCode >= 400) {
312
343
  operationLogger.warn('Client error occurred', { error, data: metadata });
313
344
  } else {
314
345
  operationLogger.info('Handled error occurred', { error, data: metadata });
315
346
  }
316
347
  } else {
317
- operationLogger.error('Unhandled error occurred', { error, data: metadata });
348
+ operationLogger.error('Unhandled error occurred', {
349
+ error,
350
+ data: metadata,
351
+ });
318
352
  }
319
353
  }
320
354
 
@@ -335,7 +369,11 @@ export class ErrorAggregator {
335
369
  * @param {string} identifier - Result identifier
336
370
  */
337
371
  addSuccess(result, identifier = null) {
338
- this.successes.push({ result, identifier, timestamp: new Date().toISOString() });
372
+ this.successes.push({
373
+ result,
374
+ identifier,
375
+ timestamp: new Date().toISOString(),
376
+ });
339
377
  }
340
378
 
341
379
  /**
@@ -344,10 +382,14 @@ export class ErrorAggregator {
344
382
  * @param {string} identifier - Error identifier
345
383
  */
346
384
  addError(error, identifier = null) {
347
- this.errors.push({ error, identifier, timestamp: new Date().toISOString() });
385
+ this.errors.push({
386
+ error,
387
+ identifier,
388
+ timestamp: new Date().toISOString(),
389
+ });
348
390
  this.logger.warn('Batch operation error', {
349
391
  error,
350
- data: { identifier, totalErrors: this.errors.length }
392
+ data: { identifier, totalErrors: this.errors.length },
351
393
  });
352
394
  }
353
395
 
@@ -362,7 +404,8 @@ export class ErrorAggregator {
362
404
  successes: this.successes.length,
363
405
  errors: this.errors.length,
364
406
  hasErrors: this.errors.length > 0,
365
- successRate: this.successes.length / (this.errors.length + this.successes.length)
407
+ successRate:
408
+ this.successes.length / (this.errors.length + this.successes.length),
366
409
  };
367
410
  }
368
411
 
@@ -380,12 +423,12 @@ export class ErrorAggregator {
380
423
  ERROR_CODES.INTERNAL_ERROR,
381
424
  {
382
425
  summary: this.getSummary(),
383
- errors: this.errors.map(e => ({
426
+ errors: this.errors.map((e) => ({
384
427
  identifier: e.identifier,
385
428
  error: e.error.message,
386
- code: e.error.code
387
- }))
388
- }
429
+ code: e.error.code,
430
+ })),
431
+ },
389
432
  );
390
433
 
391
434
  throw aggregatedError;
@@ -399,9 +442,13 @@ export class ErrorAggregator {
399
442
  const summary = this.getSummary();
400
443
 
401
444
  if (summary.hasErrors) {
402
- this.logger.warn('Batch operation completed with errors', { data: summary });
445
+ this.logger.warn('Batch operation completed with errors', {
446
+ data: summary,
447
+ });
403
448
  } else {
404
- this.logger.info('Batch operation completed successfully', { data: summary });
449
+ this.logger.info('Batch operation completed successfully', {
450
+ data: summary,
451
+ });
405
452
  }
406
453
  }
407
454
  }
@@ -418,7 +465,7 @@ export async function retryWithBackoff(fn, options = {}) {
418
465
  delay = 1000,
419
466
  backoffFactor = 2,
420
467
  maxDelay = 10000,
421
- operation = 'retry-operation'
468
+ operation = 'retry-operation',
422
469
  } = options;
423
470
 
424
471
  const operationLogger = logger.operation(operation);
@@ -431,30 +478,34 @@ export async function retryWithBackoff(fn, options = {}) {
431
478
  }
432
479
 
433
480
  return await fn();
434
-
435
481
  } catch (error) {
436
482
  lastError = error;
437
483
 
438
484
  if (attempt === retries) {
439
485
  operationLogger.error('All retry attempts failed', {
440
486
  error,
441
- data: { attempts: attempt + 1, maxRetries: retries }
487
+ data: { attempts: attempt + 1, maxRetries: retries },
442
488
  });
443
489
  break;
444
490
  }
445
491
 
446
492
  if (!isRecoverableError(error)) {
447
- operationLogger.warn('Non-recoverable error, stopping retries', { error });
493
+ operationLogger.warn('Non-recoverable error, stopping retries', {
494
+ error,
495
+ });
448
496
  break;
449
497
  }
450
498
 
451
- const currentDelay = Math.min(delay * Math.pow(backoffFactor, attempt), maxDelay);
499
+ const currentDelay = Math.min(
500
+ delay * Math.pow(backoffFactor, attempt),
501
+ maxDelay,
502
+ );
452
503
  operationLogger.debug(`Retrying in ${currentDelay}ms`, {
453
504
  error: error.message,
454
- data: { attempt: attempt + 1, delay: currentDelay }
505
+ data: { attempt: attempt + 1, delay: currentDelay },
455
506
  });
456
507
 
457
- await new Promise(resolve => setTimeout(resolve, currentDelay));
508
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
458
509
  }
459
510
  }
460
511
 
@@ -490,7 +541,7 @@ export class CircuitBreaker {
490
541
  throw new ConverseMCPError(
491
542
  `Circuit breaker is OPEN for ${this.operation}`,
492
543
  ERROR_CODES.PROVIDER_UNAVAILABLE,
493
- { state: this.state, nextAttempt: this.nextAttempt }
544
+ { state: this.state, nextAttempt: this.nextAttempt },
494
545
  );
495
546
  }
496
547
 
@@ -506,7 +557,6 @@ export class CircuitBreaker {
506
557
  }
507
558
 
508
559
  return result;
509
-
510
560
  } catch (error) {
511
561
  this.recordFailure();
512
562
  throw error;
@@ -528,8 +578,8 @@ export class CircuitBreaker {
528
578
  data: {
529
579
  failures: this.failures,
530
580
  threshold: this.failureThreshold,
531
- resetTime: new Date(this.nextAttempt).toISOString()
532
- }
581
+ resetTime: new Date(this.nextAttempt).toISOString(),
582
+ },
533
583
  });
534
584
  }
535
585
  }
@@ -556,7 +606,7 @@ export class CircuitBreaker {
556
606
  state: this.state,
557
607
  failures: this.failures,
558
608
  lastFailureTime: this.lastFailureTime,
559
- nextAttempt: this.nextAttempt
609
+ nextAttempt: this.nextAttempt,
560
610
  };
561
611
  }
562
612
  }
@@ -1,98 +1,107 @@
1
- /**
2
- * File Validator Utility
3
- *
4
- * Validates that file paths exist before processing them.
5
- * Returns early with clear error messages if any files are not found.
6
- */
7
-
8
- import { access, constants } from 'fs/promises';
9
- import { resolve, isAbsolute } from 'path';
10
- import { createToolError } from '../tools/index.js';
11
-
12
- /**
13
- * Validate that all provided file paths exist
14
- * @param {string[]} filePaths - Array of file paths to validate
15
- * @param {string} fileType - Type of files being validated (e.g., 'file', 'image')
16
- * @param {object} options - Validation options including clientCwd
17
- * @returns {Promise<{valid: boolean, missingPaths: string[], error?: object}>}
18
- */
19
- export async function validateFilePaths(filePaths, fileType = 'file', options = {}) {
20
- if (!Array.isArray(filePaths) || filePaths.length === 0) {
21
- return { valid: true, missingPaths: [] };
22
- }
23
-
24
- const missingPaths = [];
25
-
26
- for (const filePath of filePaths) {
27
- if (!filePath || typeof filePath !== 'string') {
28
- missingPaths.push(`Invalid path: ${filePath}`);
29
- continue;
30
- }
31
-
32
- // Skip validation for base64 data URLs
33
- if (filePath.startsWith('data:')) {
34
- continue;
35
- }
36
-
37
- // Convert to absolute path if needed
38
- // Use clientCwd if provided (for auto-detected client working directory), otherwise fall back to process.cwd()
39
- const absolutePath = isAbsolute(filePath)
40
- ? filePath
41
- : resolve(options.clientCwd || process.cwd(), filePath);
42
-
43
- try {
44
- // Check if file exists and is readable
45
- await access(absolutePath, constants.R_OK);
46
- } catch (error) {
47
- // Keep the original path in the error message for clarity
48
- missingPaths.push(filePath);
49
- }
50
- }
51
-
52
- if (missingPaths.length > 0) {
53
- const errorMessage = `The following ${fileType}${missingPaths.length > 1 ? 's' : ''} could not be found: ${missingPaths.join(', ')}`;
54
- return {
55
- valid: false,
56
- missingPaths,
57
- error: createToolError(errorMessage)
58
- };
59
- }
60
-
61
- return { valid: true, missingPaths: [] };
62
- }
63
-
64
- /**
65
- * Validate both files and images together
66
- * @param {object} paths - Object containing files and images arrays
67
- * @param {object} options - Validation options including clientCwd
68
- * @returns {Promise<{valid: boolean, errors: string[], errorResponse?: object}>}
69
- */
70
- export async function validateAllPaths({ files = [], images = [] }, options = {}) {
71
- const errors = [];
72
-
73
- // Validate regular files
74
- if (files.length > 0) {
75
- const fileValidation = await validateFilePaths(files, 'file', options);
76
- if (!fileValidation.valid) {
77
- errors.push(`Files not found: ${fileValidation.missingPaths.join(', ')}`);
78
- }
79
- }
80
-
81
- // Validate image files
82
- if (images.length > 0) {
83
- const imageValidation = await validateFilePaths(images, 'image', options);
84
- if (!imageValidation.valid) {
85
- errors.push(`Images not found: ${imageValidation.missingPaths.join(', ')}`);
86
- }
87
- }
88
-
89
- if (errors.length > 0) {
90
- return {
91
- valid: false,
92
- errors,
93
- errorResponse: createToolError(errors.join('. '))
94
- };
95
- }
96
-
97
- return { valid: true, errors: [] };
98
- }
1
+ /**
2
+ * File Validator Utility
3
+ *
4
+ * Validates that file paths exist before processing them.
5
+ * Returns early with clear error messages if any files are not found.
6
+ */
7
+
8
+ import { access, constants } from 'fs/promises';
9
+ import { resolve, isAbsolute } from 'path';
10
+ import { createToolError } from '../tools/index.js';
11
+
12
+ /**
13
+ * Validate that all provided file paths exist
14
+ * @param {string[]} filePaths - Array of file paths to validate
15
+ * @param {string} fileType - Type of files being validated (e.g., 'file', 'image')
16
+ * @param {object} options - Validation options including clientCwd
17
+ * @returns {Promise<{valid: boolean, missingPaths: string[], error?: object}>}
18
+ */
19
+ export async function validateFilePaths(
20
+ filePaths,
21
+ fileType = 'file',
22
+ options = {},
23
+ ) {
24
+ if (!Array.isArray(filePaths) || filePaths.length === 0) {
25
+ return { valid: true, missingPaths: [] };
26
+ }
27
+
28
+ const missingPaths = [];
29
+
30
+ for (const filePath of filePaths) {
31
+ if (!filePath || typeof filePath !== 'string') {
32
+ missingPaths.push(`Invalid path: ${filePath}`);
33
+ continue;
34
+ }
35
+
36
+ // Skip validation for base64 data URLs
37
+ if (filePath.startsWith('data:')) {
38
+ continue;
39
+ }
40
+
41
+ // Convert to absolute path if needed
42
+ // Use clientCwd if provided (for auto-detected client working directory), otherwise fall back to process.cwd()
43
+ const absolutePath = isAbsolute(filePath)
44
+ ? filePath
45
+ : resolve(options.clientCwd || process.cwd(), filePath);
46
+
47
+ try {
48
+ // Check if file exists and is readable
49
+ await access(absolutePath, constants.R_OK);
50
+ } catch (error) {
51
+ // Keep the original path in the error message for clarity
52
+ missingPaths.push(filePath);
53
+ }
54
+ }
55
+
56
+ if (missingPaths.length > 0) {
57
+ const errorMessage = `The following ${fileType}${missingPaths.length > 1 ? 's' : ''} could not be found: ${missingPaths.join(', ')}`;
58
+ return {
59
+ valid: false,
60
+ missingPaths,
61
+ error: createToolError(errorMessage),
62
+ };
63
+ }
64
+
65
+ return { valid: true, missingPaths: [] };
66
+ }
67
+
68
+ /**
69
+ * Validate both files and images together
70
+ * @param {object} paths - Object containing files and images arrays
71
+ * @param {object} options - Validation options including clientCwd
72
+ * @returns {Promise<{valid: boolean, errors: string[], errorResponse?: object}>}
73
+ */
74
+ export async function validateAllPaths(
75
+ { files = [], images = [] },
76
+ options = {},
77
+ ) {
78
+ const errors = [];
79
+
80
+ // Validate regular files
81
+ if (files.length > 0) {
82
+ const fileValidation = await validateFilePaths(files, 'file', options);
83
+ if (!fileValidation.valid) {
84
+ errors.push(`Files not found: ${fileValidation.missingPaths.join(', ')}`);
85
+ }
86
+ }
87
+
88
+ // Validate image files
89
+ if (images.length > 0) {
90
+ const imageValidation = await validateFilePaths(images, 'image', options);
91
+ if (!imageValidation.valid) {
92
+ errors.push(
93
+ `Images not found: ${imageValidation.missingPaths.join(', ')}`,
94
+ );
95
+ }
96
+ }
97
+
98
+ if (errors.length > 0) {
99
+ return {
100
+ valid: false,
101
+ errors,
102
+ errorResponse: createToolError(errors.join('. ')),
103
+ };
104
+ }
105
+
106
+ return { valid: true, errors: [] };
107
+ }