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.
- package/README.md +771 -738
- package/docs/API.md +10 -1
- package/docs/PROVIDERS.md +8 -4
- package/package.json +12 -12
- package/src/async/asyncJobStore.js +82 -52
- package/src/async/eventBus.js +25 -20
- package/src/async/fileCache.js +121 -40
- package/src/async/jobRunner.js +65 -39
- package/src/async/providerStreamNormalizer.js +203 -117
- package/src/config.js +374 -102
- package/src/continuationStore.js +32 -24
- package/src/index.js +45 -25
- package/src/prompts/helpPrompt.js +328 -305
- package/src/providers/anthropic.js +303 -119
- package/src/providers/codex.js +103 -45
- package/src/providers/deepseek.js +24 -8
- package/src/providers/google.js +337 -93
- package/src/providers/index.js +1 -1
- package/src/providers/interface.js +16 -11
- package/src/providers/mistral.js +179 -69
- package/src/providers/openai-compatible.js +231 -94
- package/src/providers/openai.js +1094 -914
- package/src/providers/openrouter-endpoints-client.js +220 -216
- package/src/providers/openrouter.js +426 -381
- package/src/providers/xai.js +153 -56
- package/src/resources/helpResource.js +70 -67
- package/src/router.js +95 -67
- package/src/services/summarizationService.js +51 -24
- package/src/systemPrompts.js +89 -89
- package/src/tools/cancelJob.js +31 -19
- package/src/tools/chat.js +997 -883
- package/src/tools/checkStatus.js +86 -65
- package/src/tools/consensus.js +400 -234
- package/src/tools/index.js +39 -16
- package/src/transport/httpTransport.js +82 -55
- package/src/utils/contextProcessor.js +54 -37
- package/src/utils/errorHandler.js +95 -45
- package/src/utils/fileValidator.js +107 -98
- package/src/utils/formatStatus.js +122 -64
- package/src/utils/logger.js +459 -449
- package/src/utils/pathUtils.js +2 -2
- 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: {
|
|
45
|
-
|
|
46
|
-
|
|
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: {
|
|
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: {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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: {
|
|
61
|
-
|
|
62
|
-
|
|
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: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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: {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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: {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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: {
|
|
94
|
-
|
|
95
|
-
|
|
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: {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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: {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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: {
|
|
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: {
|
|
120
|
-
|
|
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: {
|
|
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(
|
|
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 =
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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(
|
|
445
|
+
configLogger.debug(
|
|
446
|
+
`Auto-detected client working directory: ${detectedCwd}`,
|
|
447
|
+
);
|
|
248
448
|
} else {
|
|
249
|
-
config.server[key.toLowerCase()] = validateEnvVar(
|
|
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
|
|
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 = [
|
|
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 = [
|
|
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
|
|
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(
|
|
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', {
|
|
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 =
|
|
392
|
-
|
|
393
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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 = [
|
|
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(
|
|
796
|
+
logger.warn(
|
|
797
|
+
'[Codex] Warning: Running with danger-full-access sandbox mode - full filesystem access enabled',
|
|
798
|
+
);
|
|
552
799
|
}
|
|
553
800
|
|
|
554
|
-
if (
|
|
555
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
615
|
-
|
|
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(
|
|
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(
|
|
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)
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
|