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
package/src/config.js CHANGED
@@ -28,7 +28,7 @@ if (process.env.NODE_ENV === 'test') {
28
28
  // Configure logger early
29
29
  configureLogger({
30
30
  level: process.env.LOG_LEVEL || 'info',
31
- isDevelopment: process.env.NODE_ENV === 'development'
31
+ isDevelopment: process.env.NODE_ENV === 'development',
32
32
  });
33
33
 
34
34
  const logger = createLogger('config');
@@ -41,88 +41,283 @@ const CONFIG_SCHEMA = {
41
41
  server: {
42
42
  PORT: { type: 'number', default: 3157, description: 'Server port' },
43
43
  HOST: { type: 'string', default: 'localhost', description: 'Server host' },
44
- NODE_ENV: { type: 'string', default: 'development', description: 'Environment mode' },
45
- LOG_LEVEL: { type: 'string', default: 'info', description: 'Logging level' },
46
- CLIENT_CWD: { type: 'string', default: null, description: 'Client working directory for relative paths' },
44
+ NODE_ENV: {
45
+ type: 'string',
46
+ default: 'development',
47
+ description: 'Environment mode',
48
+ },
49
+ LOG_LEVEL: {
50
+ type: 'string',
51
+ default: 'info',
52
+ description: 'Logging level',
53
+ },
54
+ CLIENT_CWD: {
55
+ type: 'string',
56
+ default: null,
57
+ description: 'Client working directory for relative paths',
58
+ },
47
59
  },
48
60
 
49
61
  // Transport configuration
50
62
  transport: {
51
- MCP_TRANSPORT: { type: 'string', default: 'stdio', description: 'MCP transport type (stdio or http)' },
63
+ MCP_TRANSPORT: {
64
+ type: 'string',
65
+ default: 'stdio',
66
+ description: 'MCP transport type (stdio or http)',
67
+ },
52
68
 
53
69
  // HTTP server settings
54
- HTTP_PORT: { type: 'number', default: 3157, description: 'HTTP server port' },
55
- HTTP_HOST: { type: 'string', default: 'localhost', description: 'HTTP server host' },
56
- HTTP_REQUEST_TIMEOUT: { type: 'number', default: 300000, description: 'HTTP request timeout in milliseconds (5 minutes)' },
57
- HTTP_MAX_REQUEST_SIZE: { type: 'string', default: '10mb', description: 'Maximum HTTP request body size' },
70
+ HTTP_PORT: {
71
+ type: 'number',
72
+ default: 3157,
73
+ description: 'HTTP server port',
74
+ },
75
+ HTTP_HOST: {
76
+ type: 'string',
77
+ default: 'localhost',
78
+ description: 'HTTP server host',
79
+ },
80
+ HTTP_REQUEST_TIMEOUT: {
81
+ type: 'number',
82
+ default: 300000,
83
+ description: 'HTTP request timeout in milliseconds (5 minutes)',
84
+ },
85
+ HTTP_MAX_REQUEST_SIZE: {
86
+ type: 'string',
87
+ default: '10mb',
88
+ description: 'Maximum HTTP request body size',
89
+ },
58
90
 
59
91
  // Session management
60
- HTTP_SESSION_TIMEOUT: { type: 'number', default: 1800000, description: 'Session timeout in milliseconds (30 minutes)' },
61
- HTTP_SESSION_CLEANUP_INTERVAL: { type: 'number', default: 300000, description: 'Session cleanup interval in milliseconds (5 minutes)' },
62
- HTTP_MAX_CONCURRENT_SESSIONS: { type: 'number', default: 100, description: 'Maximum concurrent sessions' },
92
+ HTTP_SESSION_TIMEOUT: {
93
+ type: 'number',
94
+ default: 1800000,
95
+ description: 'Session timeout in milliseconds (30 minutes)',
96
+ },
97
+ HTTP_SESSION_CLEANUP_INTERVAL: {
98
+ type: 'number',
99
+ default: 300000,
100
+ description: 'Session cleanup interval in milliseconds (5 minutes)',
101
+ },
102
+ HTTP_MAX_CONCURRENT_SESSIONS: {
103
+ type: 'number',
104
+ default: 100,
105
+ description: 'Maximum concurrent sessions',
106
+ },
63
107
 
64
108
  // CORS configuration
65
- HTTP_ENABLE_CORS: { type: 'boolean', default: true, description: 'Enable CORS for HTTP transport' },
66
- HTTP_CORS_ORIGINS: { type: 'string', default: '*', description: 'CORS allowed origins (comma-separated)' },
67
- HTTP_CORS_METHODS: { type: 'string', default: 'GET,POST,DELETE,OPTIONS', description: 'CORS allowed methods' },
68
- HTTP_CORS_HEADERS: { type: 'string', default: 'Content-Type,mcp-session-id,Authorization', description: 'CORS allowed headers' },
69
- HTTP_CORS_CREDENTIALS: { type: 'boolean', default: false, description: 'CORS allow credentials' },
109
+ HTTP_ENABLE_CORS: {
110
+ type: 'boolean',
111
+ default: true,
112
+ description: 'Enable CORS for HTTP transport',
113
+ },
114
+ HTTP_CORS_ORIGINS: {
115
+ type: 'string',
116
+ default: '*',
117
+ description: 'CORS allowed origins (comma-separated)',
118
+ },
119
+ HTTP_CORS_METHODS: {
120
+ type: 'string',
121
+ default: 'GET,POST,DELETE,OPTIONS',
122
+ description: 'CORS allowed methods',
123
+ },
124
+ HTTP_CORS_HEADERS: {
125
+ type: 'string',
126
+ default: 'Content-Type,mcp-session-id,Authorization',
127
+ description: 'CORS allowed headers',
128
+ },
129
+ HTTP_CORS_CREDENTIALS: {
130
+ type: 'boolean',
131
+ default: false,
132
+ description: 'CORS allow credentials',
133
+ },
70
134
 
71
135
  // Security settings
72
- HTTP_DNS_REBINDING_PROTECTION: { type: 'boolean', default: false, description: 'Enable DNS rebinding protection' },
73
- HTTP_ALLOWED_HOSTS: { type: 'string', default: '127.0.0.1,localhost', description: 'Allowed hosts for DNS rebinding protection (comma-separated)' },
74
- HTTP_RATE_LIMIT_ENABLED: { type: 'boolean', default: false, description: 'Enable rate limiting' },
75
- HTTP_RATE_LIMIT_WINDOW: { type: 'number', default: 900000, description: 'Rate limit window in milliseconds (15 minutes)' },
76
- HTTP_RATE_LIMIT_MAX_REQUESTS: { type: 'number', default: 1000, description: 'Maximum requests per window' },
136
+ HTTP_DNS_REBINDING_PROTECTION: {
137
+ type: 'boolean',
138
+ default: false,
139
+ description: 'Enable DNS rebinding protection',
140
+ },
141
+ HTTP_ALLOWED_HOSTS: {
142
+ type: 'string',
143
+ default: '127.0.0.1,localhost',
144
+ description:
145
+ 'Allowed hosts for DNS rebinding protection (comma-separated)',
146
+ },
147
+ HTTP_RATE_LIMIT_ENABLED: {
148
+ type: 'boolean',
149
+ default: false,
150
+ description: 'Enable rate limiting',
151
+ },
152
+ HTTP_RATE_LIMIT_WINDOW: {
153
+ type: 'number',
154
+ default: 900000,
155
+ description: 'Rate limit window in milliseconds (15 minutes)',
156
+ },
157
+ HTTP_RATE_LIMIT_MAX_REQUESTS: {
158
+ type: 'number',
159
+ default: 1000,
160
+ description: 'Maximum requests per window',
161
+ },
77
162
  },
78
163
 
79
164
  // API Keys (at least one required)
80
165
  apiKeys: {
81
- OPENAI_API_KEY: { type: 'string', required: false, secret: true, description: 'OpenAI API key' },
82
- XAI_API_KEY: { type: 'string', required: false, secret: true, description: 'XAI API key' },
83
- GOOGLE_API_KEY: { type: 'string', required: false, secret: true, description: 'Google API key' },
84
- GEMINI_API_KEY: { type: 'string', required: false, secret: true, description: 'Gemini API key (alternative to GOOGLE_API_KEY)' },
85
- ANTHROPIC_API_KEY: { type: 'string', required: false, secret: true, description: 'Anthropic API key' },
86
- MISTRAL_API_KEY: { type: 'string', required: false, secret: true, description: 'Mistral API key' },
87
- DEEPSEEK_API_KEY: { type: 'string', required: false, secret: true, description: 'DeepSeek API key' },
88
- OPENROUTER_API_KEY: { type: 'string', required: false, secret: true, description: 'OpenRouter API key' },
166
+ OPENAI_API_KEY: {
167
+ type: 'string',
168
+ required: false,
169
+ secret: true,
170
+ description: 'OpenAI API key',
171
+ },
172
+ XAI_API_KEY: {
173
+ type: 'string',
174
+ required: false,
175
+ secret: true,
176
+ description: 'XAI API key',
177
+ },
178
+ GOOGLE_API_KEY: {
179
+ type: 'string',
180
+ required: false,
181
+ secret: true,
182
+ description: 'Google API key',
183
+ },
184
+ GEMINI_API_KEY: {
185
+ type: 'string',
186
+ required: false,
187
+ secret: true,
188
+ description: 'Gemini API key (alternative to GOOGLE_API_KEY)',
189
+ },
190
+ ANTHROPIC_API_KEY: {
191
+ type: 'string',
192
+ required: false,
193
+ secret: true,
194
+ description: 'Anthropic API key',
195
+ },
196
+ MISTRAL_API_KEY: {
197
+ type: 'string',
198
+ required: false,
199
+ secret: true,
200
+ description: 'Mistral API key',
201
+ },
202
+ DEEPSEEK_API_KEY: {
203
+ type: 'string',
204
+ required: false,
205
+ secret: true,
206
+ description: 'DeepSeek API key',
207
+ },
208
+ OPENROUTER_API_KEY: {
209
+ type: 'string',
210
+ required: false,
211
+ secret: true,
212
+ description: 'OpenRouter API key',
213
+ },
89
214
  },
90
215
 
91
216
  // Provider-specific configuration
92
217
  providers: {
93
- OPENROUTER_REFERER: { type: 'string', required: false, description: 'OpenRouter referer header for compliance' },
94
- OPENROUTER_TITLE: { type: 'string', required: false, description: 'OpenRouter X-Title header for request tracking' },
95
- OPENROUTER_DYNAMIC_MODELS: { type: 'boolean', default: false, description: 'Enable dynamic model discovery via OpenRouter endpoints API' },
218
+ OPENROUTER_REFERER: {
219
+ type: 'string',
220
+ required: false,
221
+ description: 'OpenRouter referer header for compliance',
222
+ },
223
+ OPENROUTER_TITLE: {
224
+ type: 'string',
225
+ required: false,
226
+ description: 'OpenRouter X-Title header for request tracking',
227
+ },
228
+ OPENROUTER_DYNAMIC_MODELS: {
229
+ type: 'boolean',
230
+ default: false,
231
+ description:
232
+ 'Enable dynamic model discovery via OpenRouter endpoints API',
233
+ },
96
234
 
97
235
  // Google Vertex AI configuration
98
- GOOGLE_GENAI_USE_VERTEXAI: { type: 'boolean', default: false, description: 'Use Google Vertex AI instead of Gemini Developer API' },
99
- GOOGLE_CLOUD_PROJECT: { type: 'string', required: false, description: 'Google Cloud project ID for Vertex AI' },
100
- GOOGLE_CLOUD_LOCATION: { type: 'string', required: false, description: 'Google Cloud location for Vertex AI (e.g., us-central1)' },
101
- GOOGLE_API_VERSION: { type: 'string', default: 'v1beta', description: 'Google API version (v1, v1beta, v1alpha)' },
236
+ GOOGLE_GENAI_USE_VERTEXAI: {
237
+ type: 'boolean',
238
+ default: false,
239
+ description: 'Use Google Vertex AI instead of Gemini Developer API',
240
+ },
241
+ GOOGLE_CLOUD_PROJECT: {
242
+ type: 'string',
243
+ required: false,
244
+ description: 'Google Cloud project ID for Vertex AI',
245
+ },
246
+ GOOGLE_CLOUD_LOCATION: {
247
+ type: 'string',
248
+ required: false,
249
+ description: 'Google Cloud location for Vertex AI (e.g., us-central1)',
250
+ },
251
+ GOOGLE_API_VERSION: {
252
+ type: 'string',
253
+ default: 'v1beta',
254
+ description: 'Google API version (v1, v1beta, v1alpha)',
255
+ },
102
256
 
103
257
  // Codex configuration
104
- CODEX_API_KEY: { type: 'string', required: false, secret: true, description: 'Codex API key (alternative to ChatGPT login)' },
105
- CODEX_SANDBOX_MODE: { type: 'string', default: 'read-only', description: 'Codex sandbox mode (read-only | workspace-write | danger-full-access)' },
106
- CODEX_SKIP_GIT_CHECK: { type: 'boolean', default: true, description: 'Skip Git repository validation check' },
107
- CODEX_APPROVAL_POLICY: { type: 'string', default: 'never', description: 'Approval policy (never | untrusted | on-failure | on-request)' },
108
- CODEX_DEFAULT_MODEL: { type: 'string', default: 'gpt-5-codex', description: 'Default Codex model' }
258
+ CODEX_API_KEY: {
259
+ type: 'string',
260
+ required: false,
261
+ secret: true,
262
+ description: 'Codex API key (alternative to ChatGPT login)',
263
+ },
264
+ CODEX_SANDBOX_MODE: {
265
+ type: 'string',
266
+ default: 'read-only',
267
+ description:
268
+ 'Codex sandbox mode (read-only | workspace-write | danger-full-access)',
269
+ },
270
+ CODEX_SKIP_GIT_CHECK: {
271
+ type: 'boolean',
272
+ default: true,
273
+ description: 'Skip Git repository validation check',
274
+ },
275
+ CODEX_APPROVAL_POLICY: {
276
+ type: 'string',
277
+ default: 'never',
278
+ description:
279
+ 'Approval policy (never | untrusted | on-failure | on-request)',
280
+ },
281
+ CODEX_DEFAULT_MODEL: {
282
+ type: 'string',
283
+ default: 'gpt-5-codex',
284
+ description: 'Default Codex model',
285
+ },
109
286
  },
110
287
 
111
-
112
288
  // MCP configuration
113
289
  mcp: {
114
- MAX_MCP_OUTPUT_TOKENS: { type: 'number', default: 25000, description: 'Maximum tokens in MCP tool responses' },
290
+ MAX_MCP_OUTPUT_TOKENS: {
291
+ type: 'number',
292
+ default: 25000,
293
+ description: 'Maximum tokens in MCP tool responses',
294
+ },
115
295
  },
116
296
 
117
297
  // Summarization configuration
118
298
  summarization: {
119
- ENABLE_RESPONSE_SUMMARIZATION: { type: 'boolean', default: false, description: 'Enable AI-powered response summarization for async operations' },
120
- SUMMARIZATION_MODEL: { type: 'string', default: 'gpt-5-nano', description: 'Model to use for summarization tasks (title generation, streaming summaries, final summaries)' },
299
+ ENABLE_RESPONSE_SUMMARIZATION: {
300
+ type: 'boolean',
301
+ default: false,
302
+ description:
303
+ 'Enable AI-powered response summarization for async operations',
304
+ },
305
+ SUMMARIZATION_MODEL: {
306
+ type: 'string',
307
+ default: 'gpt-5-nano',
308
+ description:
309
+ 'Model to use for summarization tasks (title generation, streaming summaries, final summaries)',
310
+ },
121
311
  },
122
312
 
123
313
  // Async tools configuration
124
314
  async: {
125
- DISABLE_ASYNC_TOOLS: { type: 'boolean', default: false, description: 'Disable async execution support (removes async parameter from tools and status check tools)' },
315
+ DISABLE_ASYNC_TOOLS: {
316
+ type: 'boolean',
317
+ default: false,
318
+ description:
319
+ 'Disable async execution support (removes async parameter from tools and status check tools)',
320
+ },
126
321
  },
127
322
  };
128
323
 
@@ -139,7 +334,9 @@ function validateEnvVar(key, value, schema) {
139
334
  // Handle missing values
140
335
  if (value === undefined || value === '') {
141
336
  if (schema.required) {
142
- throw new ConfigurationError(`Required environment variable ${key} is missing`);
337
+ throw new ConfigurationError(
338
+ `Required environment variable ${key} is missing`,
339
+ );
143
340
  }
144
341
  return schema.default;
145
342
  }
@@ -152,7 +349,7 @@ function validateEnvVar(key, value, schema) {
152
349
  const num = parseInt(value, 10);
153
350
  if (isNaN(num)) {
154
351
  throw new ConfigurationError(
155
- `Environment variable ${key} must be a valid number, got: ${value}`
352
+ `Environment variable ${key} must be a valid number, got: ${value}`,
156
353
  );
157
354
  }
158
355
  return num;
@@ -160,7 +357,7 @@ function validateEnvVar(key, value, schema) {
160
357
  const lower = value.toLowerCase();
161
358
  if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lower)) {
162
359
  throw new ConfigurationError(
163
- `Environment variable ${key} must be a boolean value, got: ${value}`
360
+ `Environment variable ${key} must be a boolean value, got: ${value}`,
164
361
  );
165
362
  }
166
363
  return ['true', '1', 'yes'].includes(lower);
@@ -239,14 +436,21 @@ export async function loadConfig() {
239
436
  // When run via npx, INIT_CWD contains the directory where npx was invoked
240
437
  // PWD is another common variable set to the working directory
241
438
  // npm_config_local_prefix is set when run via npm/npx
242
- const detectedCwd = process.env.INIT_CWD ||
243
- process.env.PWD ||
244
- process.env.npm_config_local_prefix ||
245
- process.cwd();
439
+ const detectedCwd =
440
+ process.env.INIT_CWD ||
441
+ process.env.PWD ||
442
+ process.env.npm_config_local_prefix ||
443
+ process.cwd();
246
444
  config.server.client_cwd = detectedCwd;
247
- configLogger.debug(`Auto-detected client working directory: ${detectedCwd}`);
445
+ configLogger.debug(
446
+ `Auto-detected client working directory: ${detectedCwd}`,
447
+ );
248
448
  } else {
249
- config.server[key.toLowerCase()] = validateEnvVar(key, process.env[key], schema);
449
+ config.server[key.toLowerCase()] = validateEnvVar(
450
+ key,
451
+ process.env[key],
452
+ schema,
453
+ );
250
454
  }
251
455
  } catch (error) {
252
456
  errors.push(error.message);
@@ -262,7 +466,10 @@ export async function loadConfig() {
262
466
  config.transport.mcptransport = value;
263
467
  } else if (key.startsWith('HTTP_')) {
264
468
  // Convert HTTP_PORT -> port, HTTP_CORS_ORIGINS -> corsorigins, etc.
265
- const configKey = key.replace('HTTP_', '').toLowerCase().replace(/_/g, '');
469
+ const configKey = key
470
+ .replace('HTTP_', '')
471
+ .toLowerCase()
472
+ .replace(/_/g, '');
266
473
  config.transport[configKey] = value;
267
474
  }
268
475
  } catch (error) {
@@ -304,20 +511,29 @@ export async function loadConfig() {
304
511
 
305
512
  // Validate Codex sandbox mode during loading
306
513
  if (key === 'CODEX_SANDBOX_MODE') {
307
- const validSandboxModes = ['read-only', 'workspace-write', 'danger-full-access'];
514
+ const validSandboxModes = [
515
+ 'read-only',
516
+ 'workspace-write',
517
+ 'danger-full-access',
518
+ ];
308
519
  if (!validSandboxModes.includes(value)) {
309
520
  errors.push(
310
- `Invalid CODEX_SANDBOX_MODE: "${value}". Must be one of: ${validSandboxModes.join(', ')}`
521
+ `Invalid CODEX_SANDBOX_MODE: "${value}". Must be one of: ${validSandboxModes.join(', ')}`,
311
522
  );
312
523
  }
313
524
  }
314
525
 
315
526
  // Validate Codex approval policy during loading
316
527
  if (key === 'CODEX_APPROVAL_POLICY') {
317
- const validPolicies = ['never', 'untrusted', 'on-failure', 'on-request'];
528
+ const validPolicies = [
529
+ 'never',
530
+ 'untrusted',
531
+ 'on-failure',
532
+ 'on-request',
533
+ ];
318
534
  if (!validPolicies.includes(value)) {
319
535
  errors.push(
320
- `Invalid CODEX_APPROVAL_POLICY: "${value}". Must be one of: ${validPolicies.join(', ')}`
536
+ `Invalid CODEX_APPROVAL_POLICY: "${value}". Must be one of: ${validPolicies.join(', ')}`,
321
537
  );
322
538
  }
323
539
  }
@@ -331,7 +547,9 @@ export async function loadConfig() {
331
547
  for (const [key, schema] of Object.entries(CONFIG_SCHEMA.mcp)) {
332
548
  try {
333
549
  const value = validateEnvVar(key, process.env[key], schema);
334
- const configKey = key.replace('MAX_MCP_OUTPUT_TOKENS', 'max_mcp_output_tokens').toLowerCase();
550
+ const configKey = key
551
+ .replace('MAX_MCP_OUTPUT_TOKENS', 'max_mcp_output_tokens')
552
+ .toLowerCase();
335
553
  config.mcp[configKey] = value;
336
554
  } catch (error) {
337
555
  errors.push(error.message);
@@ -367,7 +585,10 @@ export async function loadConfig() {
367
585
 
368
586
  // Load name and version from package.json
369
587
  try {
370
- const packagePath = join(dirname(fileURLToPath(import.meta.url)), '../package.json');
588
+ const packagePath = join(
589
+ dirname(fileURLToPath(import.meta.url)),
590
+ '../package.json',
591
+ );
371
592
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
372
593
  config.mcp.name = packageJson.name || 'converse-mcp-server';
373
594
  config.mcp.version = packageJson.version || 'unknown';
@@ -375,7 +596,9 @@ export async function loadConfig() {
375
596
  // Fallback values if package.json can't be read
376
597
  config.mcp.name = 'converse-mcp-server';
377
598
  config.mcp.version = 'unknown';
378
- configLogger.warn('Could not read package.json for name/version', { error: error.message });
599
+ configLogger.warn('Could not read package.json for name/version', {
600
+ error: error.message,
601
+ });
379
602
  }
380
603
 
381
604
  // Set environment flags
@@ -388,13 +611,14 @@ export async function loadConfig() {
388
611
 
389
612
  // Validate that at least one API key is present OR Vertex AI is configured
390
613
  const availableKeys = Object.keys(config.apiKeys);
391
- const hasVertexAI = config.providers.googlegenaiusevertexai &&
392
- config.providers.googlecloudproject &&
393
- config.providers.googlecloudlocation;
614
+ const hasVertexAI =
615
+ config.providers.googlegenaiusevertexai &&
616
+ config.providers.googlecloudproject &&
617
+ config.providers.googlecloudlocation;
394
618
 
395
619
  if (availableKeys.length === 0 && !hasVertexAI) {
396
620
  errors.push(
397
- 'At least one API key must be configured: OPENAI_API_KEY, XAI_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, ANTHROPIC_API_KEY, MISTRAL_API_KEY, DEEPSEEK_API_KEY, or OPENROUTER_API_KEY. Alternatively, configure Google Vertex AI with GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT, and GOOGLE_CLOUD_LOCATION.'
621
+ 'At least one API key must be configured: OPENAI_API_KEY, XAI_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, ANTHROPIC_API_KEY, MISTRAL_API_KEY, DEEPSEEK_API_KEY, or OPENROUTER_API_KEY. Alternatively, configure Google Vertex AI with GOOGLE_GENAI_USE_VERTEXAI, GOOGLE_CLOUD_PROJECT, and GOOGLE_CLOUD_LOCATION.',
398
622
  );
399
623
  }
400
624
 
@@ -409,15 +633,17 @@ export async function loadConfig() {
409
633
  // Validate API key formats
410
634
  for (const [provider, apiKey] of Object.entries(config.apiKeys)) {
411
635
  if (!validateApiKeyFormat(provider, apiKey)) {
412
- errors.push(`Invalid API key format for ${provider.toUpperCase()}_API_KEY`);
636
+ errors.push(
637
+ `Invalid API key format for ${provider.toUpperCase()}_API_KEY`,
638
+ );
413
639
  }
414
640
  }
415
641
 
416
642
  // Throw accumulated errors
417
643
  if (errors.length > 0) {
418
644
  throw new ConfigurationError(
419
- `Configuration validation failed with ${errors.length} error(s):\n${errors.map(e => ` - ${e}`).join('\n')}`,
420
- { errors }
645
+ `Configuration validation failed with ${errors.length} error(s):\n${errors.map((e) => ` - ${e}`).join('\n')}`,
646
+ { errors },
421
647
  );
422
648
  }
423
649
 
@@ -426,13 +652,15 @@ export async function loadConfig() {
426
652
  configLogger.info('Configuration loaded successfully');
427
653
 
428
654
  return config;
429
-
430
655
  } catch (error) {
431
656
  configLogger.error('Configuration loading failed', { error });
432
657
  if (error instanceof ConfigurationError) {
433
658
  throw error;
434
659
  }
435
- throw new ConfigurationError(`Failed to load configuration: ${error.message}`, { originalError: error });
660
+ throw new ConfigurationError(
661
+ `Failed to load configuration: ${error.message}`,
662
+ { originalError: error },
663
+ );
436
664
  }
437
665
  }
438
666
 
@@ -445,10 +673,23 @@ export function getHttpTransportConfig(config) {
445
673
  const transport = config.transport;
446
674
 
447
675
  // Parse comma-separated values
448
- const corsOrigins = transport.corsorigins === '*' ? '*' : transport.corsorigins?.split(',').map(o => o.trim()) || ['*'];
449
- const corsMethods = transport.corsmethods?.split(',').map(m => m.trim()) || ['GET', 'POST', 'DELETE', 'OPTIONS'];
450
- const corsHeaders = transport.corsheaders?.split(',').map(h => h.trim()) || ['Content-Type', 'mcp-session-id', 'Authorization'];
451
- const allowedHosts = transport.allowedhosts?.split(',').map(h => h.trim()) || ['127.0.0.1', 'localhost'];
676
+ const corsOrigins =
677
+ transport.corsorigins === '*'
678
+ ? '*'
679
+ : transport.corsorigins?.split(',').map((o) => o.trim()) || ['*'];
680
+ const corsMethods = transport.corsmethods
681
+ ?.split(',')
682
+ .map((m) => m.trim()) || ['GET', 'POST', 'DELETE', 'OPTIONS'];
683
+ const corsHeaders = transport.corsheaders
684
+ ?.split(',')
685
+ .map((h) => h.trim()) || [
686
+ 'Content-Type',
687
+ 'mcp-session-id',
688
+ 'Authorization',
689
+ ];
690
+ const allowedHosts = transport.allowedhosts
691
+ ?.split(',')
692
+ .map((h) => h.trim()) || ['127.0.0.1', 'localhost'];
452
693
 
453
694
  return {
454
695
  // Server settings
@@ -516,8 +757,8 @@ export function isProviderAvailable(config, providerName) {
516
757
  * @returns {string[]} Array of available provider names
517
758
  */
518
759
  export function getAvailableProviders(config) {
519
- return Object.keys(config.apiKeys).filter(provider =>
520
- isProviderAvailable(config, provider)
760
+ return Object.keys(config.apiKeys).filter((provider) =>
761
+ isProviderAvailable(config, provider),
521
762
  );
522
763
  }
523
764
 
@@ -531,10 +772,14 @@ function validateCodexConfig(config) {
531
772
  const approvalPolicy = config.providers?.codexapprovalpolicy;
532
773
 
533
774
  // Validate sandbox mode
534
- const validSandboxModes = ['read-only', 'workspace-write', 'danger-full-access'];
775
+ const validSandboxModes = [
776
+ 'read-only',
777
+ 'workspace-write',
778
+ 'danger-full-access',
779
+ ];
535
780
  if (sandbox && !validSandboxModes.includes(sandbox)) {
536
781
  throw new ConfigurationError(
537
- `Invalid CODEX_SANDBOX_MODE: "${sandbox}". Must be one of: ${validSandboxModes.join(', ')}`
782
+ `Invalid CODEX_SANDBOX_MODE: "${sandbox}". Must be one of: ${validSandboxModes.join(', ')}`,
538
783
  );
539
784
  }
540
785
 
@@ -542,17 +787,24 @@ function validateCodexConfig(config) {
542
787
  const validPolicies = ['never', 'untrusted', 'on-failure', 'on-request'];
543
788
  if (approvalPolicy && !validPolicies.includes(approvalPolicy)) {
544
789
  throw new ConfigurationError(
545
- `Invalid CODEX_APPROVAL_POLICY: "${approvalPolicy}". Must be one of: ${validPolicies.join(', ')}`
790
+ `Invalid CODEX_APPROVAL_POLICY: "${approvalPolicy}". Must be one of: ${validPolicies.join(', ')}`,
546
791
  );
547
792
  }
548
793
 
549
794
  // Warn about dangerous configurations
550
795
  if (sandbox === 'danger-full-access') {
551
- logger.warn('[Codex] Warning: Running with danger-full-access sandbox mode - full filesystem access enabled');
796
+ logger.warn(
797
+ '[Codex] Warning: Running with danger-full-access sandbox mode - full filesystem access enabled',
798
+ );
552
799
  }
553
800
 
554
- if ((approvalPolicy === 'on-request' || approvalPolicy === 'untrusted') && config.transport.mcptransport !== 'stdio') {
555
- logger.warn(`[Codex] Warning: approval policy '${approvalPolicy}' may cause hangs in headless/server mode`);
801
+ if (
802
+ (approvalPolicy === 'on-request' || approvalPolicy === 'untrusted') &&
803
+ config.transport.mcptransport !== 'stdio'
804
+ ) {
805
+ logger.warn(
806
+ `[Codex] Warning: approval policy '${approvalPolicy}' may cause hangs in headless/server mode`,
807
+ );
556
808
  }
557
809
  }
558
810
 
@@ -569,14 +821,16 @@ export async function validateRuntimeConfig(config) {
569
821
 
570
822
  // Validate server configuration
571
823
  if (config.server.port < 1 || config.server.port > 65535) {
572
- throw new ConfigurationError(`Invalid port number: ${config.server.port}`);
824
+ throw new ConfigurationError(
825
+ `Invalid port number: ${config.server.port}`,
826
+ );
573
827
  }
574
828
 
575
829
  // Validate environment
576
830
  const validEnvs = ['development', 'production', 'test'];
577
831
  if (!validEnvs.includes(config.environment.nodeEnv)) {
578
832
  throw new ConfigurationError(
579
- `Invalid NODE_ENV: ${config.environment.nodeEnv}. Must be one of: ${validEnvs.join(', ')}`
833
+ `Invalid NODE_ENV: ${config.environment.nodeEnv}. Must be one of: ${validEnvs.join(', ')}`,
580
834
  );
581
835
  }
582
836
 
@@ -584,7 +838,7 @@ export async function validateRuntimeConfig(config) {
584
838
  const validLogLevels = ['silent', 'error', 'warn', 'info', 'debug'];
585
839
  if (!validLogLevels.includes(config.server.log_level)) {
586
840
  throw new ConfigurationError(
587
- `Invalid LOG_LEVEL: ${config.server.log_level}. Must be one of: ${validLogLevels.join(', ')}`
841
+ `Invalid LOG_LEVEL: ${config.server.log_level}. Must be one of: ${validLogLevels.join(', ')}`,
588
842
  );
589
843
  }
590
844
 
@@ -594,35 +848,52 @@ export async function validateRuntimeConfig(config) {
594
848
 
595
849
  // Validate HTTP port
596
850
  if (httpConfig.port < 1 || httpConfig.port > 65535) {
597
- throw new ConfigurationError(`Invalid HTTP_PORT: ${httpConfig.port}. Must be between 1 and 65535`);
851
+ throw new ConfigurationError(
852
+ `Invalid HTTP_PORT: ${httpConfig.port}. Must be between 1 and 65535`,
853
+ );
598
854
  }
599
855
 
600
856
  // Validate timeouts
601
857
  if (httpConfig.requestTimeout < 1000) {
602
- throw new ConfigurationError(`Invalid HTTP_REQUEST_TIMEOUT: ${httpConfig.requestTimeout}. Must be at least 1000ms`);
858
+ throw new ConfigurationError(
859
+ `Invalid HTTP_REQUEST_TIMEOUT: ${httpConfig.requestTimeout}. Must be at least 1000ms`,
860
+ );
603
861
  }
604
862
 
605
863
  if (httpConfig.sessionTimeout < 60000) {
606
- throw new ConfigurationError(`Invalid HTTP_SESSION_TIMEOUT: ${httpConfig.sessionTimeout}. Must be at least 60000ms (1 minute)`);
864
+ throw new ConfigurationError(
865
+ `Invalid HTTP_SESSION_TIMEOUT: ${httpConfig.sessionTimeout}. Must be at least 60000ms (1 minute)`,
866
+ );
607
867
  }
608
868
 
609
869
  if (httpConfig.sessionCleanupInterval < 10000) {
610
- throw new ConfigurationError(`Invalid HTTP_SESSION_CLEANUP_INTERVAL: ${httpConfig.sessionCleanupInterval}. Must be at least 10000ms (10 seconds)`);
870
+ throw new ConfigurationError(
871
+ `Invalid HTTP_SESSION_CLEANUP_INTERVAL: ${httpConfig.sessionCleanupInterval}. Must be at least 10000ms (10 seconds)`,
872
+ );
611
873
  }
612
874
 
613
875
  // Validate max concurrent sessions
614
- if (httpConfig.maxConcurrentSessions < 1 || httpConfig.maxConcurrentSessions > 10000) {
615
- throw new ConfigurationError(`Invalid HTTP_MAX_CONCURRENT_SESSIONS: ${httpConfig.maxConcurrentSessions}. Must be between 1 and 10000`);
876
+ if (
877
+ httpConfig.maxConcurrentSessions < 1 ||
878
+ httpConfig.maxConcurrentSessions > 10000
879
+ ) {
880
+ throw new ConfigurationError(
881
+ `Invalid HTTP_MAX_CONCURRENT_SESSIONS: ${httpConfig.maxConcurrentSessions}. Must be between 1 and 10000`,
882
+ );
616
883
  }
617
884
 
618
885
  // Validate rate limiting
619
886
  if (httpConfig.rateLimitEnabled) {
620
887
  if (httpConfig.rateLimitWindow < 1000) {
621
- throw new ConfigurationError(`Invalid HTTP_RATE_LIMIT_WINDOW: ${httpConfig.rateLimitWindow}. Must be at least 1000ms`);
888
+ throw new ConfigurationError(
889
+ `Invalid HTTP_RATE_LIMIT_WINDOW: ${httpConfig.rateLimitWindow}. Must be at least 1000ms`,
890
+ );
622
891
  }
623
892
 
624
893
  if (httpConfig.rateLimitMaxRequests < 1) {
625
- throw new ConfigurationError(`Invalid HTTP_RATE_LIMIT_MAX_REQUESTS: ${httpConfig.rateLimitMaxRequests}. Must be at least 1`);
894
+ throw new ConfigurationError(
895
+ `Invalid HTTP_RATE_LIMIT_MAX_REQUESTS: ${httpConfig.rateLimitMaxRequests}. Must be at least 1`,
896
+ );
626
897
  }
627
898
  }
628
899
  }
@@ -637,7 +908,6 @@ export async function validateRuntimeConfig(config) {
637
908
  }
638
909
 
639
910
  return true;
640
-
641
911
  } catch (error) {
642
912
  if (error instanceof ConfigurationError) {
643
913
  throw error;
@@ -667,11 +937,13 @@ function logConfigurationSummary(config) {
667
937
  logLevel: config.server.log_level,
668
938
  availableProviders: availableProviders.join(', ') || 'none',
669
939
  mcpServer: `${config.mcp.name} v${config.mcp.version}`,
670
- apiKeys: Object.keys(config.apiKeys).map(key => {
671
- const value = config.apiKeys[key];
672
- return `${key.toUpperCase()}: ${value ? `${value.substring(0, 8)}...` : 'not configured'}`;
673
- }).join(', ')
674
- }
940
+ apiKeys: Object.keys(config.apiKeys)
941
+ .map((key) => {
942
+ const value = config.apiKeys[key];
943
+ return `${key.toUpperCase()}: ${value ? `${value.substring(0, 8)}...` : 'not configured'}`;
944
+ })
945
+ .join(', '),
946
+ },
675
947
  });
676
948
  }
677
949