converse-mcp-server 2.9.6 → 2.9.7

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": "converse-mcp-server",
3
- "version": "2.9.6",
3
+ "version": "2.9.7",
4
4
  "description": "Converse MCP Server - Converse with other LLMs with chat and consensus tools",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -11,7 +11,6 @@ import { promises as fs } from 'fs';
11
11
  import path from 'path';
12
12
  import { debugLog, debugError } from '../utils/console.js';
13
13
  import { ConverseMCPError, ERROR_CODES } from '../utils/errorHandler.js';
14
- import { isSafeIdSegment } from '../utils/idValidation.js';
15
14
 
16
15
  /**
17
16
  * File cache specific error class
@@ -150,14 +149,6 @@ export class FileCache extends FileCacheInterface {
150
149
  * @private
151
150
  */
152
151
  getJobDir(jobId) {
153
- if (!isSafeIdSegment(jobId)) {
154
- throw new FileCacheError(
155
- 'Job ID contains unsafe characters',
156
- ERROR_CODES.VALIDATION_ERROR,
157
- { jobId },
158
- );
159
- }
160
-
161
152
  const today = new Date().toISOString().split('T')[0]; // yyyy-mm-dd
162
153
  return path.join(this.baseDir, today, jobId);
163
154
  }
@@ -210,15 +201,7 @@ export class FileCache extends FileCacheInterface {
210
201
  if (!jobId || typeof jobId !== 'string') {
211
202
  throw new FileCacheError(
212
203
  'Job ID must be a non-empty string',
213
- ERROR_CODES.VALIDATION_ERROR,
214
- { jobId },
215
- );
216
- }
217
-
218
- if (!isSafeIdSegment(jobId)) {
219
- throw new FileCacheError(
220
- 'Job ID contains unsafe characters',
221
- ERROR_CODES.VALIDATION_ERROR,
204
+ ERROR_CODES.CACHE_WRITE_FAILED,
222
205
  { jobId },
223
206
  );
224
207
  }
@@ -282,15 +265,7 @@ export class FileCache extends FileCacheInterface {
282
265
  if (!jobId || typeof jobId !== 'string') {
283
266
  throw new FileCacheError(
284
267
  'Job ID must be a non-empty string',
285
- ERROR_CODES.VALIDATION_ERROR,
286
- { jobId },
287
- );
288
- }
289
-
290
- if (!isSafeIdSegment(jobId)) {
291
- throw new FileCacheError(
292
- 'Job ID contains unsafe characters',
293
- ERROR_CODES.VALIDATION_ERROR,
268
+ ERROR_CODES.CACHE_WRITE_FAILED,
294
269
  { jobId },
295
270
  );
296
271
  }
@@ -349,15 +324,7 @@ export class FileCache extends FileCacheInterface {
349
324
  if (!jobId || typeof jobId !== 'string') {
350
325
  throw new FileCacheError(
351
326
  'Job ID must be a non-empty string',
352
- ERROR_CODES.VALIDATION_ERROR,
353
- { jobId },
354
- );
355
- }
356
-
357
- if (!isSafeIdSegment(jobId)) {
358
- throw new FileCacheError(
359
- 'Job ID contains unsafe characters',
360
- ERROR_CODES.VALIDATION_ERROR,
327
+ ERROR_CODES.CACHE_READ_FAILED,
361
328
  { jobId },
362
329
  );
363
330
  }
@@ -284,7 +284,7 @@ export const codexProvider = {
284
284
  const rawWorkingDirectory = config.server?.client_cwd || process.cwd();
285
285
  // Normalize Windows extended-length paths (\\?\C:\...) to regular paths
286
286
  const workingDirectory = normalizeExtendedPath(rawWorkingDirectory);
287
- const sandbox = config.providers?.codexsandboxmode || 'read-only';
287
+ const sandboxMode = config.providers?.codexsandboxmode || 'read-only';
288
288
  const skipGitRepoCheck =
289
289
  config.providers?.codexskipgitcheck !== undefined
290
290
  ? config.providers.codexskipgitcheck
@@ -294,7 +294,7 @@ export const codexProvider = {
294
294
  // Create or resume thread
295
295
  const threadOptions = {
296
296
  workingDirectory,
297
- sandbox,
297
+ sandboxMode,
298
298
  skipGitRepoCheck,
299
299
  approvalPolicy,
300
300
  };
package/src/tools/chat.js CHANGED
@@ -21,7 +21,6 @@ import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
21
21
  import { validateAllPaths } from '../utils/fileValidator.js';
22
22
  import { SummarizationService } from '../services/summarizationService.js';
23
23
  import { exportConversation } from '../utils/conversationExporter.js';
24
- import { isRecoverableError, retryWithBackoff } from '../utils/errorHandler.js';
25
24
 
26
25
  const logger = createLogger('chat');
27
26
 
@@ -266,14 +265,12 @@ export async function chatTool(args, dependencies) {
266
265
 
267
266
  messages.push(userMessage);
268
267
 
269
- // Select provider(s)
268
+ // Select provider
270
269
  let selectedProvider;
271
270
  let providerName;
272
271
 
273
- const providerCandidates = [];
274
-
275
272
  if (model === 'auto') {
276
- // Auto-select providers in priority order
273
+ // Auto-select first available provider in priority order
277
274
  // Prioritize subscription-based providers (codex, gemini-cli, claude) over API-key providers
278
275
  const providerOrder = [
279
276
  'codex',
@@ -291,17 +288,20 @@ export async function chatTool(args, dependencies) {
291
288
  for (const name of providerOrder) {
292
289
  const provider = providers[name];
293
290
  if (provider && provider.isAvailable && provider.isAvailable(config)) {
294
- providerCandidates.push({ name, provider });
291
+ providerName = name;
292
+ selectedProvider = provider;
293
+ break;
295
294
  }
296
295
  }
297
296
 
298
- if (providerCandidates.length === 0) {
297
+ if (!providerName) {
299
298
  return createToolError(
300
299
  'No providers available. Please configure at least one API key.',
301
300
  );
302
301
  }
303
302
  } else {
304
303
  // Use specified provider/model
304
+ // Try to map model to provider
305
305
  providerName = mapModelToProvider(model, providers);
306
306
  selectedProvider = providers[providerName];
307
307
 
@@ -314,60 +314,32 @@ export async function chatTool(args, dependencies) {
314
314
  `Provider ${providerName} is not available. Check API key configuration.`,
315
315
  );
316
316
  }
317
-
318
- providerCandidates.push({ name: providerName, provider: selectedProvider });
319
317
  }
320
318
 
321
- // Call provider with recovery (retry and, for auto, failover)
319
+ // Resolve model name and prepare provider options
320
+ const resolvedModel = resolveAutoModel(model, providerName);
321
+ const providerOptions = {
322
+ model: resolvedModel,
323
+ temperature,
324
+ reasoning_effort,
325
+ verbosity,
326
+ use_websearch,
327
+ config,
328
+ continuation_id, // Pass for thread resumption
329
+ continuationStore, // Pass store for state management
330
+ };
331
+
332
+ // Call provider
322
333
  let response;
323
334
  const startTime = Date.now();
324
- let lastError;
325
- let resolvedModel;
326
-
327
- for (const candidate of providerCandidates) {
328
- providerName = candidate.name;
329
- selectedProvider = candidate.provider;
330
-
331
- // Resolve model name and prepare provider options
332
- resolvedModel = resolveAutoModel(model, providerName);
333
- const providerOptions = {
334
- model: resolvedModel,
335
- temperature,
336
- reasoning_effort,
337
- verbosity,
338
- use_websearch,
339
- config,
340
- continuation_id, // Pass for thread resumption
341
- continuationStore, // Pass store for state management
342
- };
343
-
344
- try {
345
- response = await retryWithBackoff(
346
- () => selectedProvider.invoke(messages, providerOptions),
347
- getProviderRetryOptions(config, providerName),
348
- );
349
- break;
350
- } catch (error) {
351
- lastError = error;
352
- logger.error('Provider error', {
353
- error,
354
- data: { provider: providerName },
355
- });
356
-
357
- if (
358
- model !== 'auto' ||
359
- !shouldFailoverToNextProvider(error) ||
360
- candidate === providerCandidates[providerCandidates.length - 1]
361
- ) {
362
- return createToolError(`Provider error: ${error.message}`);
363
- }
364
- }
365
- }
366
-
367
- if (!response) {
368
- return createToolError(
369
- `Provider error: ${(lastError && lastError.message) || 'Unknown error'}`,
370
- );
335
+ try {
336
+ response = await selectedProvider.invoke(messages, providerOptions);
337
+ } catch (error) {
338
+ logger.error('Provider error', {
339
+ error,
340
+ data: { provider: providerName },
341
+ });
342
+ return createToolError(`Provider error: ${error.message}`);
371
343
  }
372
344
  const executionTime = (Date.now() - startTime) / 1000; // Convert to seconds
373
345
 
@@ -500,29 +472,6 @@ function resolveAutoModel(model, providerName) {
500
472
  return defaults[providerName] || 'gpt-5';
501
473
  }
502
474
 
503
- function getProviderRetryOptions(config, providerName) {
504
- const nodeEnv = config?.environment?.nodeEnv || process.env.NODE_ENV;
505
- const isTest = nodeEnv === 'test';
506
-
507
- return {
508
- retries: isTest ? 1 : 3,
509
- delay: isTest ? 0 : 500,
510
- maxDelay: isTest ? 0 : 10000,
511
- operation: `provider-invoke:${providerName}`,
512
- };
513
- }
514
-
515
- function shouldFailoverToNextProvider(error) {
516
- if (isRecoverableError(error)) {
517
- return true;
518
- }
519
-
520
- const message = (error && error.message) || '';
521
- return /(api key|authentication|unauthorized|forbidden|invalid|not available)/i.test(
522
- message,
523
- );
524
- }
525
-
526
475
  export function mapModelToProvider(model, providers) {
527
476
  const modelLower = model.toLowerCase();
528
477
 
@@ -17,7 +17,6 @@ import {
17
17
  formatJobListHumanReadable,
18
18
  formatConversationHistory,
19
19
  } from '../utils/formatStatus.js';
20
- import { isSafeIdSegment } from '../utils/idValidation.js';
21
20
 
22
21
  const logger = createLogger('check-status');
23
22
 
@@ -37,22 +36,8 @@ export async function checkStatusTool(args, dependencies) {
37
36
  const { continuation_id, full_history = false } = args;
38
37
 
39
38
  // Validate arguments
40
- if (continuation_id !== undefined) {
41
- if (typeof continuation_id !== 'string') {
42
- return createToolError('continuation_id must be a string');
43
- }
44
-
45
- if (continuation_id.length === 0) {
46
- return createToolError(
47
- 'Invalid continuation_id: must be a non-empty string',
48
- );
49
- }
50
-
51
- if (!isSafeIdSegment(continuation_id)) {
52
- return createToolError(
53
- 'Invalid continuation_id: contains unsafe characters',
54
- );
55
- }
39
+ if (continuation_id && typeof continuation_id !== 'string') {
40
+ return createToolError('continuation_id must be a string');
56
41
  }
57
42
 
58
43
  const asyncJobStore = getAsyncJobStore();
@@ -1,32 +0,0 @@
1
- /**
2
- * ID validation helpers
3
- *
4
- * These are intentionally conservative and are primarily used to ensure IDs that
5
- * are used as filesystem path segments cannot escape their intended directory.
6
- */
7
-
8
- /**
9
- * Check whether an ID is safe to use as a single filesystem path segment.
10
- *
11
- * Allowed characters: A–Z a–z 0–9 _ -
12
- * Disallowed: path separators, dots, whitespace, and other punctuation.
13
- *
14
- * @param {unknown} id
15
- * @param {object} [options]
16
- * @param {number} [options.maxLength]
17
- * @returns {boolean}
18
- */
19
- export function isSafeIdSegment(id, options = {}) {
20
- const { maxLength = 128 } = options;
21
-
22
- if (typeof id !== 'string') {
23
- return false;
24
- }
25
-
26
- if (id.length === 0 || id.length > maxLength) {
27
- return false;
28
- }
29
-
30
- return /^[A-Za-z0-9_-]+$/.test(id);
31
- }
32
-