converse-mcp-server 1.0.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/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "converse-mcp-server",
3
+ "version": "1.0.1",
4
+ "description": "Simplified, functional Node.js implementation of MCP server with chat and consensus tools",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "converse": "bin/converse.js",
9
+ "converse-mcp-server": "bin/converse.js"
10
+ },
11
+ "engines": {
12
+ "node": ">=20.0.0"
13
+ },
14
+ "scripts": {
15
+ "kill-server": "node scripts/kill-server.js",
16
+ "start": "npm run kill-server && node src/index.js",
17
+ "start:clean": "node src/index.js",
18
+ "start:port": "PORT=3001 npm run start:clean",
19
+ "dev": "npm run kill-server && NODE_ENV=development LOG_LEVEL=debug node --watch src/index.js",
20
+ "dev:clean": "NODE_ENV=development LOG_LEVEL=debug node --watch src/index.js",
21
+ "dev:port": "PORT=3001 npm run dev:clean",
22
+ "dev:quiet": "npm run kill-server && NODE_ENV=development LOG_LEVEL=info node --watch src/index.js",
23
+ "dev:verbose": "npm run kill-server && NODE_ENV=development LOG_LEVEL=trace node --watch src/index.js",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "test:unit": "vitest run --config vitest.unit.config.js",
27
+ "test:integration": "vitest run --config vitest.integration.config.js",
28
+ "test:mcp-client": "vitest run --config vitest.mcp-client.config.js",
29
+ "test:real-api": "vitest run --config vitest.real-api.config.js",
30
+ "test:ci": "vitest run --config vitest.ci.config.js",
31
+ "test:providers": "vitest run tests/providers",
32
+ "test:tools": "vitest run tests/tools",
33
+ "test:coverage": "vitest run --coverage",
34
+ "test:coverage:unit": "vitest run --config vitest.unit.config.js --coverage",
35
+ "test:coverage:integration": "vitest run --config vitest.integration.config.js --coverage",
36
+ "test:coverage:mcp-client": "vitest run --config vitest.mcp-client.config.js --coverage",
37
+ "test:ui": "vitest --ui",
38
+ "lint": "eslint src/ tests/",
39
+ "lint:fix": "eslint src/ tests/ --fix",
40
+ "lint:watch": "nodemon --exec \"npm run lint\" --watch src --watch tests",
41
+ "format": "prettier --write src/ tests/ *.md *.json",
42
+ "format:check": "prettier --check src/ tests/ *.md *.json",
43
+ "typecheck": "node --check src/**/*.js",
44
+ "validate:simple": "npm run typecheck && npm run lint && npm run format:check && npm run test",
45
+ "clean": "rm -rf node_modules package-lock.json && npm install",
46
+ "debug": "NODE_ENV=development LOG_LEVEL=trace node --inspect src/index.js",
47
+ "debug:break": "NODE_ENV=development LOG_LEVEL=trace node --inspect-brk src/index.js",
48
+ "check-deps": "npm outdated",
49
+ "update-deps": "npm update",
50
+ "security-audit": "npm audit",
51
+ "build": "node scripts/build.js",
52
+ "build:fast": "node scripts/build.js --skip-tests",
53
+ "dev-server": "node dev-server.js",
54
+ "validate": "node scripts/validate.js",
55
+ "validate:fix": "node scripts/validate.js --fix",
56
+ "validate:fast": "node scripts/validate.js --skip-tests --skip-lint",
57
+ "precommit": "npm run validate"
58
+ },
59
+ "keywords": [
60
+ "mcp",
61
+ "server",
62
+ "ai",
63
+ "chat",
64
+ "consensus",
65
+ "openai",
66
+ "google",
67
+ "gemini",
68
+ "grok"
69
+ ],
70
+ "author": "Converse MCP Server",
71
+ "license": "MIT",
72
+ "homepage": "https://github.com/FallDownTheSystem/converse#readme",
73
+ "repository": {
74
+ "type": "git",
75
+ "url": "git+https://github.com/FallDownTheSystem/converse.git"
76
+ },
77
+ "bugs": {
78
+ "url": "https://github.com/FallDownTheSystem/converse/issues"
79
+ },
80
+ "files": [
81
+ "src/",
82
+ "bin/",
83
+ "docs/",
84
+ "README.md",
85
+ ".env.example"
86
+ ],
87
+ "dependencies": {
88
+ "@google/genai": "^1.11.0",
89
+ "@modelcontextprotocol/sdk": "^1.17.0",
90
+ "cors": "^2.8.5",
91
+ "dotenv": "^16.4.5",
92
+ "express": "^5.1.0",
93
+ "openai": "^5.0.0"
94
+ },
95
+ "devDependencies": {
96
+ "@vitest/coverage-v8": "^3.2.4",
97
+ "eslint": "^9.0.0",
98
+ "prettier": "^3.0.0",
99
+ "vitest": "^3.2.4"
100
+ }
101
+ }
package/src/config.js ADDED
@@ -0,0 +1,521 @@
1
+ /**
2
+ * Configuration Management System
3
+ *
4
+ * Comprehensive environment-based configuration system for the Converse MCP Server.
5
+ * Loads, validates, and manages all configuration from environment variables only.
6
+ * Follows functional architecture with explicit dependencies.
7
+ */
8
+
9
+ import dotenv from 'dotenv';
10
+ import { createLogger, configureLogger } from './utils/logger.js';
11
+ import { ConfigurationError } from './utils/errorHandler.js';
12
+
13
+ // Load environment variables from appropriate .env file
14
+ // Priority: .env.test (for test env) > .env (default)
15
+ if (process.env.NODE_ENV === 'test') {
16
+ // Load test environment first
17
+ dotenv.config({ path: '.env.test' });
18
+ // Fall back to .env for any missing variables
19
+ dotenv.config({ override: false });
20
+ } else {
21
+ // Load default .env file
22
+ dotenv.config();
23
+ }
24
+
25
+ // Configure logger early
26
+ configureLogger({
27
+ level: process.env.LOG_LEVEL || 'info',
28
+ isDevelopment: process.env.NODE_ENV === 'development'
29
+ });
30
+
31
+ const logger = createLogger('config');
32
+
33
+ /**
34
+ * Configuration schema defining all supported environment variables
35
+ */
36
+ const CONFIG_SCHEMA = {
37
+ // Server configuration
38
+ server: {
39
+ PORT: { type: 'number', default: 3000, description: 'Server port' },
40
+ HOST: { type: 'string', default: 'localhost', description: 'Server host' },
41
+ NODE_ENV: { type: 'string', default: 'development', description: 'Environment mode' },
42
+ LOG_LEVEL: { type: 'string', default: 'info', description: 'Logging level' },
43
+ },
44
+
45
+ // Transport configuration
46
+ transport: {
47
+ MCP_TRANSPORT: { type: 'string', default: 'http', description: 'MCP transport type (http or stdio)' },
48
+
49
+ // HTTP server settings
50
+ HTTP_PORT: { type: 'number', default: 3000, description: 'HTTP server port' },
51
+ HTTP_HOST: { type: 'string', default: 'localhost', description: 'HTTP server host' },
52
+ HTTP_REQUEST_TIMEOUT: { type: 'number', default: 300000, description: 'HTTP request timeout in milliseconds (5 minutes)' },
53
+ HTTP_MAX_REQUEST_SIZE: { type: 'string', default: '10mb', description: 'Maximum HTTP request body size' },
54
+
55
+ // Session management
56
+ HTTP_SESSION_TIMEOUT: { type: 'number', default: 1800000, description: 'Session timeout in milliseconds (30 minutes)' },
57
+ HTTP_SESSION_CLEANUP_INTERVAL: { type: 'number', default: 300000, description: 'Session cleanup interval in milliseconds (5 minutes)' },
58
+ HTTP_MAX_CONCURRENT_SESSIONS: { type: 'number', default: 100, description: 'Maximum concurrent sessions' },
59
+
60
+ // CORS configuration
61
+ HTTP_ENABLE_CORS: { type: 'boolean', default: true, description: 'Enable CORS for HTTP transport' },
62
+ HTTP_CORS_ORIGINS: { type: 'string', default: '*', description: 'CORS allowed origins (comma-separated)' },
63
+ HTTP_CORS_METHODS: { type: 'string', default: 'GET,POST,DELETE,OPTIONS', description: 'CORS allowed methods' },
64
+ HTTP_CORS_HEADERS: { type: 'string', default: 'Content-Type,mcp-session-id,Authorization', description: 'CORS allowed headers' },
65
+ HTTP_CORS_CREDENTIALS: { type: 'boolean', default: false, description: 'CORS allow credentials' },
66
+
67
+ // Security settings
68
+ HTTP_DNS_REBINDING_PROTECTION: { type: 'boolean', default: false, description: 'Enable DNS rebinding protection' },
69
+ HTTP_ALLOWED_HOSTS: { type: 'string', default: '127.0.0.1,localhost', description: 'Allowed hosts for DNS rebinding protection (comma-separated)' },
70
+ HTTP_RATE_LIMIT_ENABLED: { type: 'boolean', default: false, description: 'Enable rate limiting' },
71
+ HTTP_RATE_LIMIT_WINDOW: { type: 'number', default: 900000, description: 'Rate limit window in milliseconds (15 minutes)' },
72
+ HTTP_RATE_LIMIT_MAX_REQUESTS: { type: 'number', default: 1000, description: 'Maximum requests per window' },
73
+ },
74
+
75
+ // API Keys (at least one required)
76
+ apiKeys: {
77
+ OPENAI_API_KEY: { type: 'string', required: false, secret: true, description: 'OpenAI API key' },
78
+ XAI_API_KEY: { type: 'string', required: false, secret: true, description: 'XAI API key' },
79
+ GOOGLE_API_KEY: { type: 'string', required: false, secret: true, description: 'Google API key' },
80
+ },
81
+
82
+ // Provider-specific configuration
83
+ providers: {
84
+ GOOGLE_LOCATION: { type: 'string', default: 'us-central1', description: 'Google Cloud location' },
85
+ XAI_BASE_URL: { type: 'string', default: 'https://api.x.ai/v1', description: 'XAI API base URL' },
86
+ },
87
+
88
+ // MCP configuration
89
+ mcp: {
90
+ MCP_SERVER_NAME: { type: 'string', default: 'converse-mcp-server', description: 'MCP server name' },
91
+ MCP_SERVER_VERSION: { type: 'string', default: '1.0.0', description: 'MCP server version' },
92
+ MAX_MCP_OUTPUT_TOKENS: { type: 'number', default: 25000, description: 'Maximum tokens in MCP tool responses' },
93
+ },
94
+ };
95
+
96
+ // ConfigurationError now imported from errorHandler
97
+
98
+ /**
99
+ * Validates and parses environment variable value according to schema
100
+ * @param {string} key - Environment variable key
101
+ * @param {string|undefined} value - Environment variable value
102
+ * @param {object} schema - Schema definition for the variable
103
+ * @returns {any} Parsed and validated value
104
+ */
105
+ function validateEnvVar(key, value, schema) {
106
+ // Handle missing values
107
+ if (value === undefined || value === '') {
108
+ if (schema.required) {
109
+ throw new ConfigurationError(`Required environment variable ${key} is missing`);
110
+ }
111
+ return schema.default;
112
+ }
113
+
114
+ // Type validation and conversion
115
+ switch (schema.type) {
116
+ case 'string':
117
+ return value;
118
+ case 'number':
119
+ const num = parseInt(value, 10);
120
+ if (isNaN(num)) {
121
+ throw new ConfigurationError(
122
+ `Environment variable ${key} must be a valid number, got: ${value}`
123
+ );
124
+ }
125
+ return num;
126
+ case 'boolean':
127
+ const lower = value.toLowerCase();
128
+ if (!['true', 'false', '1', '0', 'yes', 'no'].includes(lower)) {
129
+ throw new ConfigurationError(
130
+ `Environment variable ${key} must be a boolean value, got: ${value}`
131
+ );
132
+ }
133
+ return ['true', '1', 'yes'].includes(lower);
134
+ default:
135
+ return value;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Validates API key format and basic structure
141
+ * @param {string} provider - Provider name
142
+ * @param {string} apiKey - API key to validate
143
+ * @returns {boolean} True if API key appears valid
144
+ */
145
+ function validateApiKeyFormat(provider, apiKey) {
146
+ if (!apiKey || typeof apiKey !== 'string') {
147
+ return false;
148
+ }
149
+
150
+ // Basic format validation for each provider
151
+ switch (provider) {
152
+ case 'openai':
153
+ return apiKey.startsWith('sk-') && apiKey.length > 20;
154
+ case 'xai':
155
+ return apiKey.startsWith('xai-') && apiKey.length > 20;
156
+ case 'google':
157
+ return apiKey.length > 20; // Google keys vary in format
158
+ default:
159
+ return apiKey.length >= 10; // Basic minimum length check
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Loads and validates complete configuration from environment variables
165
+ * @returns {Promise<object>} Validated configuration object
166
+ * @throws {ConfigurationError} If configuration is invalid or incomplete
167
+ */
168
+ export async function loadConfig() {
169
+ const configLogger = logger.operation('loadConfig');
170
+ configLogger.debug('Starting configuration loading');
171
+
172
+ const config = {
173
+ server: {},
174
+ transport: {},
175
+ apiKeys: {},
176
+ providers: {},
177
+ mcp: {},
178
+ environment: {
179
+ isDevelopment: false,
180
+ isProduction: false,
181
+ nodeEnv: '',
182
+ },
183
+ };
184
+
185
+ const errors = [];
186
+
187
+ try {
188
+ // Load server configuration
189
+ for (const [key, schema] of Object.entries(CONFIG_SCHEMA.server)) {
190
+ try {
191
+ config.server[key.toLowerCase()] = validateEnvVar(key, process.env[key], schema);
192
+ } catch (error) {
193
+ errors.push(error.message);
194
+ }
195
+ }
196
+
197
+ // Load transport configuration
198
+ for (const [key, schema] of Object.entries(CONFIG_SCHEMA.transport)) {
199
+ try {
200
+ const value = validateEnvVar(key, process.env[key], schema);
201
+
202
+ if (key === 'MCP_TRANSPORT') {
203
+ config.transport.mcptransport = value;
204
+ } else if (key.startsWith('HTTP_')) {
205
+ // Convert HTTP_PORT -> port, HTTP_CORS_ORIGINS -> corsorigins, etc.
206
+ const configKey = key.replace('HTTP_', '').toLowerCase().replace(/_/g, '');
207
+ config.transport[configKey] = value;
208
+ }
209
+ } catch (error) {
210
+ errors.push(error.message);
211
+ }
212
+ }
213
+
214
+ // Load API keys
215
+ for (const [key, schema] of Object.entries(CONFIG_SCHEMA.apiKeys)) {
216
+ try {
217
+ const value = validateEnvVar(key, process.env[key], schema);
218
+ if (value) {
219
+ const providerName = key.replace('_API_KEY', '').toLowerCase();
220
+ config.apiKeys[providerName] = value;
221
+ }
222
+ } catch (error) {
223
+ errors.push(error.message);
224
+ }
225
+ }
226
+
227
+ // Load provider configuration
228
+ for (const [key, schema] of Object.entries(CONFIG_SCHEMA.providers)) {
229
+ try {
230
+ const value = validateEnvVar(key, process.env[key], schema);
231
+ const configKey = key.toLowerCase().replace(/_/g, '');
232
+ config.providers[configKey] = value;
233
+ } catch (error) {
234
+ errors.push(error.message);
235
+ }
236
+ }
237
+
238
+ // Load MCP configuration
239
+ for (const [key, schema] of Object.entries(CONFIG_SCHEMA.mcp)) {
240
+ try {
241
+ const value = validateEnvVar(key, process.env[key], schema);
242
+ const configKey = key.replace('MCP_SERVER_', '').toLowerCase();
243
+ config.mcp[configKey] = value;
244
+ } catch (error) {
245
+ errors.push(error.message);
246
+ }
247
+ }
248
+
249
+ // Set environment flags
250
+ const nodeEnv = config.server.node_env || 'development';
251
+ config.environment = {
252
+ isDevelopment: nodeEnv === 'development',
253
+ isProduction: nodeEnv === 'production',
254
+ nodeEnv,
255
+ };
256
+
257
+ // Validate that at least one API key is present
258
+ const availableKeys = Object.keys(config.apiKeys);
259
+ if (availableKeys.length === 0) {
260
+ errors.push(
261
+ 'At least one API key must be configured: OPENAI_API_KEY, XAI_API_KEY, or GOOGLE_API_KEY'
262
+ );
263
+ }
264
+
265
+ // Validate API key formats
266
+ for (const [provider, apiKey] of Object.entries(config.apiKeys)) {
267
+ if (!validateApiKeyFormat(provider, apiKey)) {
268
+ errors.push(`Invalid API key format for ${provider.toUpperCase()}_API_KEY`);
269
+ }
270
+ }
271
+
272
+ // Throw accumulated errors
273
+ if (errors.length > 0) {
274
+ throw new ConfigurationError(
275
+ `Configuration validation failed with ${errors.length} error(s):\n${errors.map(e => ` - ${e}`).join('\n')}`,
276
+ { errors }
277
+ );
278
+ }
279
+
280
+ // Log configuration summary (without secrets)
281
+ logConfigurationSummary(config);
282
+ configLogger.info('Configuration loaded successfully');
283
+
284
+ return config;
285
+
286
+ } catch (error) {
287
+ configLogger.error('Configuration loading failed', { error });
288
+ if (error instanceof ConfigurationError) {
289
+ throw error;
290
+ }
291
+ throw new ConfigurationError(`Failed to load configuration: ${error.message}`, { originalError: error });
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Gets HTTP transport configuration with proper structure
297
+ * @param {object} config - Main configuration object
298
+ * @returns {object} HTTP transport configuration
299
+ */
300
+ export function getHttpTransportConfig(config) {
301
+ const transport = config.transport;
302
+
303
+ // Parse comma-separated values
304
+ const corsOrigins = transport.corsorigins === '*' ? '*' : transport.corsorigins?.split(',').map(o => o.trim()) || ['*'];
305
+ const corsMethods = transport.corsmethods?.split(',').map(m => m.trim()) || ['GET', 'POST', 'DELETE', 'OPTIONS'];
306
+ const corsHeaders = transport.corsheaders?.split(',').map(h => h.trim()) || ['Content-Type', 'mcp-session-id', 'Authorization'];
307
+ const allowedHosts = transport.allowedhosts?.split(',').map(h => h.trim()) || ['127.0.0.1', 'localhost'];
308
+
309
+ return {
310
+ // Server settings
311
+ port: transport.port || 3000,
312
+ host: transport.host || 'localhost',
313
+ requestTimeout: transport.requesttimeout || 300000,
314
+ maxRequestSize: transport.maxrequestsize || '10mb',
315
+
316
+ // Session management
317
+ sessionTimeout: transport.sessiontimeout || 1800000,
318
+ sessionCleanupInterval: transport.sessioncleanupinterval || 300000,
319
+ maxConcurrentSessions: transport.maxconcurrentsessions || 100,
320
+
321
+ // CORS configuration
322
+ enableCors: transport.enablecors !== false,
323
+ corsOptions: {
324
+ origin: corsOrigins,
325
+ methods: corsMethods,
326
+ allowedHeaders: corsHeaders,
327
+ credentials: transport.corscredentials || false,
328
+ exposedHeaders: ['Mcp-Session-Id'],
329
+ },
330
+
331
+ // Security settings
332
+ enableDnsRebindingProtection: transport.dnsrebindingprotection || false,
333
+ allowedHosts: allowedHosts,
334
+ rateLimitEnabled: transport.ratelimitenabled || false,
335
+ rateLimitWindow: transport.ratelimitwindow || 900000,
336
+ rateLimitMaxRequests: transport.ratelimitmaxrequests || 1000,
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Gets configuration for a specific provider
342
+ * @param {object} config - Main configuration object
343
+ * @param {string} providerName - Name of the provider
344
+ * @returns {object} Provider-specific configuration
345
+ */
346
+ export function getProviderConfig(config, providerName) {
347
+ const apiKey = config.apiKeys[providerName];
348
+ const providerConfig = {};
349
+
350
+ // Add provider-specific configuration
351
+ switch (providerName) {
352
+ case 'google':
353
+ providerConfig.location = config.providers.googlelocation;
354
+ break;
355
+ case 'xai':
356
+ providerConfig.baseUrl = config.providers.xaibaseurl;
357
+ break;
358
+ }
359
+
360
+ return {
361
+ apiKey,
362
+ ...providerConfig,
363
+ };
364
+ }
365
+
366
+ /**
367
+ * Checks if a provider is available (has valid API key)
368
+ * @param {object} config - Main configuration object
369
+ * @param {string} providerName - Name of the provider
370
+ * @returns {boolean} True if provider is available
371
+ */
372
+ export function isProviderAvailable(config, providerName) {
373
+ const apiKey = config.apiKeys[providerName];
374
+ return apiKey && validateApiKeyFormat(providerName, apiKey);
375
+ }
376
+
377
+ /**
378
+ * Gets list of available providers
379
+ * @param {object} config - Main configuration object
380
+ * @returns {string[]} Array of available provider names
381
+ */
382
+ export function getAvailableProviders(config) {
383
+ return Object.keys(config.apiKeys).filter(provider =>
384
+ isProviderAvailable(config, provider)
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Validates runtime configuration consistency
390
+ * @param {object} config - Configuration object to validate
391
+ * @returns {Promise<boolean>} True if configuration is valid
392
+ * @throws {ConfigurationError} If configuration is invalid
393
+ */
394
+ export async function validateRuntimeConfig(config) {
395
+ try {
396
+ // Validate server configuration
397
+ if (config.server.port < 1 || config.server.port > 65535) {
398
+ throw new ConfigurationError(`Invalid port number: ${config.server.port}`);
399
+ }
400
+
401
+ // Validate environment
402
+ const validEnvs = ['development', 'production', 'test'];
403
+ if (!validEnvs.includes(config.environment.nodeEnv)) {
404
+ throw new ConfigurationError(
405
+ `Invalid NODE_ENV: ${config.environment.nodeEnv}. Must be one of: ${validEnvs.join(', ')}`
406
+ );
407
+ }
408
+
409
+ // Validate log level
410
+ const validLogLevels = ['silent', 'error', 'warn', 'info', 'debug'];
411
+ if (!validLogLevels.includes(config.server.log_level)) {
412
+ throw new ConfigurationError(
413
+ `Invalid LOG_LEVEL: ${config.server.log_level}. Must be one of: ${validLogLevels.join(', ')}`
414
+ );
415
+ }
416
+
417
+ // Validate HTTP transport configuration
418
+ if (config.transport.mcptransport === 'http') {
419
+ const httpConfig = getHttpTransportConfig(config);
420
+
421
+ // Validate HTTP port
422
+ if (httpConfig.port < 1 || httpConfig.port > 65535) {
423
+ throw new ConfigurationError(`Invalid HTTP_PORT: ${httpConfig.port}. Must be between 1 and 65535`);
424
+ }
425
+
426
+ // Validate timeouts
427
+ if (httpConfig.requestTimeout < 1000) {
428
+ throw new ConfigurationError(`Invalid HTTP_REQUEST_TIMEOUT: ${httpConfig.requestTimeout}. Must be at least 1000ms`);
429
+ }
430
+
431
+ if (httpConfig.sessionTimeout < 60000) {
432
+ throw new ConfigurationError(`Invalid HTTP_SESSION_TIMEOUT: ${httpConfig.sessionTimeout}. Must be at least 60000ms (1 minute)`);
433
+ }
434
+
435
+ if (httpConfig.sessionCleanupInterval < 10000) {
436
+ throw new ConfigurationError(`Invalid HTTP_SESSION_CLEANUP_INTERVAL: ${httpConfig.sessionCleanupInterval}. Must be at least 10000ms (10 seconds)`);
437
+ }
438
+
439
+ // Validate max concurrent sessions
440
+ if (httpConfig.maxConcurrentSessions < 1 || httpConfig.maxConcurrentSessions > 10000) {
441
+ throw new ConfigurationError(`Invalid HTTP_MAX_CONCURRENT_SESSIONS: ${httpConfig.maxConcurrentSessions}. Must be between 1 and 10000`);
442
+ }
443
+
444
+ // Validate rate limiting
445
+ if (httpConfig.rateLimitEnabled) {
446
+ if (httpConfig.rateLimitWindow < 1000) {
447
+ throw new ConfigurationError(`Invalid HTTP_RATE_LIMIT_WINDOW: ${httpConfig.rateLimitWindow}. Must be at least 1000ms`);
448
+ }
449
+
450
+ if (httpConfig.rateLimitMaxRequests < 1) {
451
+ throw new ConfigurationError(`Invalid HTTP_RATE_LIMIT_MAX_REQUESTS: ${httpConfig.rateLimitMaxRequests}. Must be at least 1`);
452
+ }
453
+ }
454
+ }
455
+
456
+ // Production-specific validations
457
+ if (config.environment.isProduction) {
458
+ // Require at least 2 providers in production for redundancy
459
+ const availableProviders = getAvailableProviders(config);
460
+ if (availableProviders.length < 1) {
461
+ console.warn('Warning: Only one provider configured in production environment');
462
+ }
463
+ }
464
+
465
+ return true;
466
+
467
+ } catch (error) {
468
+ if (error instanceof ConfigurationError) {
469
+ throw error;
470
+ }
471
+ throw new ConfigurationError(`Runtime validation failed: ${error.message}`);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Logs configuration summary (masking sensitive information)
477
+ * Only logs when NOT in MCP stdio mode to avoid interfering with JSON-RPC protocol
478
+ * @param {object} config - Configuration object
479
+ */
480
+ function logConfigurationSummary(config) {
481
+ // Skip logging when running as MCP server to avoid interfering with JSON-RPC
482
+ if (process.stdin.isTTY === false || process.env.NODE_ENV === 'test') {
483
+ return;
484
+ }
485
+
486
+ const availableProviders = getAvailableProviders(config);
487
+
488
+ // Only output configuration summary if not in silent mode
489
+ if (config.server.log_level !== 'silent') {
490
+ console.error('Configuration loaded successfully:');
491
+ console.error(` Environment: ${config.environment.nodeEnv}`);
492
+ console.error(` Port: ${config.server.port}`);
493
+ console.error(` Log Level: ${config.server.log_level}`);
494
+ console.error(` Available Providers: ${availableProviders.join(', ') || 'none'}`);
495
+ console.error(` MCP Server: ${config.mcp.name} v${config.mcp.version}`);
496
+
497
+ // Mask API keys in logs
498
+ const maskedKeys = Object.keys(config.apiKeys).map(key => {
499
+ const value = config.apiKeys[key];
500
+ return `${key.toUpperCase()}: ${value ? `${value.substring(0, 8)}...` : 'not configured'}`;
501
+ });
502
+ console.error(` API Keys: ${maskedKeys.join(', ')}`);
503
+ }
504
+ }
505
+
506
+ /**
507
+ * Creates MCP client configuration object
508
+ * @param {object} config - Main configuration object
509
+ * @returns {object} MCP client configuration
510
+ */
511
+ export function getMcpClientConfig(config) {
512
+ return {
513
+ name: config.mcp.name,
514
+ version: config.mcp.version,
515
+ capabilities: {
516
+ tools: {},
517
+ },
518
+ environment: config.environment.nodeEnv,
519
+ providers: getAvailableProviders(config),
520
+ };
521
+ }