tryaii-mcp-server
Version:
TryAII MCP Server - 15+ AI models with comparison, cost tracking, and collective intelligence
209 lines ⢠10.7 kB
JavaScript
import dotenv from 'dotenv';
// šļø CLEAN CONFIGURATION LOADING
// Load configurations in order of precedence:
// 1. Server configuration (infrastructure only)
// 2. User API keys (optional, user-provided)
// 3. TryAII fallback keys (secure, internal)
// Load config files if they exist (silent fail for production deployments)
if (process.env.NODE_ENV !== 'production') {
try {
dotenv.config({ path: 'config/server.env' });
}
catch { }
try {
dotenv.config({ path: 'config/user-keys.env' });
}
catch { }
try {
dotenv.config({ path: 'config/tryaii-keys.env' });
}
catch { }
}
// Load production env file if it exists
try {
dotenv.config({ path: '.env.production' });
}
catch { }
const getEnvVar = (key, defaultValue, required = false) => {
const value = process.env[key] || defaultValue;
if (required && !value) {
throw new Error(`Required environment variable ${key} is not set`);
}
return value || '';
};
const getEnvNumber = (key, defaultValue) => {
const value = process.env[key];
if (!value)
return defaultValue;
const parsed = parseInt(value, 10);
if (isNaN(parsed)) {
throw new Error(`Environment variable ${key} must be a valid number`);
}
return parsed;
};
// š Smart API key resolution with clear precedence
const resolveApiKeys = () => {
return {
user: {
...(process.env.USER_OPENAI_API_KEY && process.env.USER_OPENAI_API_KEY !== 'your_openai_key_here' && { openai: process.env.USER_OPENAI_API_KEY }),
...(process.env.USER_ANTHROPIC_API_KEY && process.env.USER_ANTHROPIC_API_KEY !== 'your_anthropic_key_here' && { anthropic: process.env.USER_ANTHROPIC_API_KEY }),
...(process.env.USER_GOOGLE_API_KEY && process.env.USER_GOOGLE_API_KEY !== 'your_google_key_here' && { google: process.env.USER_GOOGLE_API_KEY }),
...(process.env.USER_DEEPSEEK_API_KEY && process.env.USER_DEEPSEEK_API_KEY !== 'your_deepseek_key_here' && { deepseek: process.env.USER_DEEPSEEK_API_KEY }),
...(process.env.USER_XAI_API_KEY && process.env.USER_XAI_API_KEY !== 'your_xai_key_here' && { xai: process.env.USER_XAI_API_KEY }),
...(process.env.USER_MISTRAL_API_KEY && process.env.USER_MISTRAL_API_KEY !== 'your_mistral_key_here' && { mistral: process.env.USER_MISTRAL_API_KEY }),
...(process.env.USER_BRAVE_API_KEY && process.env.USER_BRAVE_API_KEY !== 'your_brave_key_here' && { brave: process.env.USER_BRAVE_API_KEY })
},
tryaii: {
...(process.env.TRYAII_OPENAI_API_KEY && process.env.TRYAII_OPENAI_API_KEY !== 'your-tryaii-openai-key-here' && { openai: process.env.TRYAII_OPENAI_API_KEY }),
...(process.env.TRYAII_ANTHROPIC_API_KEY && process.env.TRYAII_ANTHROPIC_API_KEY !== 'your-tryaii-anthropic-key-here' && { anthropic: process.env.TRYAII_ANTHROPIC_API_KEY }),
...(process.env.TRYAII_GOOGLE_API_KEY && process.env.TRYAII_GOOGLE_API_KEY !== 'your-tryaii-google-key-here' && { google: process.env.TRYAII_GOOGLE_API_KEY }),
...(process.env.TRYAII_DEEPSEEK_API_KEY && process.env.TRYAII_DEEPSEEK_API_KEY !== 'your-tryaii-deepseek-key-here' && { deepseek: process.env.TRYAII_DEEPSEEK_API_KEY }),
...(process.env.TRYAII_XAI_API_KEY && process.env.TRYAII_XAI_API_KEY !== 'your-tryaii-xai-key-here' && { xai: process.env.TRYAII_XAI_API_KEY }),
...(process.env.TRYAII_MISTRAL_API_KEY && process.env.TRYAII_MISTRAL_API_KEY !== 'your-tryaii-mistral-key-here' && { mistral: process.env.TRYAII_MISTRAL_API_KEY }),
...(process.env.TRYAII_BRAVE_API_KEY && process.env.TRYAII_BRAVE_API_KEY !== 'your-tryaii-brave-key-here' && { brave: process.env.TRYAII_BRAVE_API_KEY })
}
};
};
// š§ Generate MCP Process Environment Variables
const generateMcpEnvironment = (apiKeys) => {
const env = {};
// Use user keys first (priority), then fallback to TryAII keys
const resolveKey = (provider) => {
return apiKeys.user[provider] || apiKeys.tryaii[provider];
};
// Map to MCP environment variable names
const openaiKey = resolveKey('openai');
if (openaiKey)
env.OPENAI_API_KEY = openaiKey;
const anthropicKey = resolveKey('anthropic');
if (anthropicKey)
env.ANTHROPIC_API_KEY = anthropicKey;
const googleKey = resolveKey('google');
if (googleKey)
env.GOOGLE_API_KEY = googleKey;
const deepseekKey = resolveKey('deepseek');
if (deepseekKey)
env.DEEPSEEK_API_KEY = deepseekKey;
const xaiKey = resolveKey('xai');
if (xaiKey)
env.XAI_API_KEY = xaiKey;
const mistralKey = resolveKey('mistral');
if (mistralKey)
env.MISTRAL_API_KEY = mistralKey;
const braveKey = resolveKey('brave');
if (braveKey)
env.BRAVE_API_KEY = braveKey;
return env;
};
export const config = {
server: {
port: getEnvNumber('PORT', getEnvNumber('MIDDLEWARE_PORT', 3001)),
host: getEnvVar('MIDDLEWARE_HOST', '0.0.0.0'),
nodeEnv: getEnvVar('NODE_ENV', 'development'),
corsOrigins: getEnvVar('CORS_ORIGINS', '*').split(','),
modes: {
http: getEnvVar('SERVER_MODE_HTTP', 'true') === 'true',
mcp: getEnvVar('SERVER_MODE_MCP', 'true') === 'true',
...(process.env.MCP_PROTOCOL_PORT && { mcpPort: getEnvNumber('MCP_PROTOCOL_PORT', 3002) })
}
},
mcp: {
executablePath: getEnvVar('MCP_EXECUTABLE_PATH', '', false), // Optional for npm package
timeout: getEnvNumber('MCP_PROCESS_TIMEOUT', 30000),
maxRestarts: getEnvNumber('MCP_MAX_RESTARTS', 5),
restartDelay: 1000,
env: generateMcpEnvironment(resolveApiKeys()) // š§ Dynamic API key resolution
},
http: {
baseURL: getEnvVar('MCP_HTTP_BASE_URL', getEnvVar('NODE_ENV', 'development') === 'production'
? 'https://tryaii.onrender.com'
: 'http://localhost:4000'),
timeout: getEnvNumber('HTTP_CLIENT_TIMEOUT', 30000),
maxRetries: getEnvNumber('HTTP_CLIENT_MAX_RETRIES', 3),
retryDelay: getEnvNumber('HTTP_CLIENT_RETRY_DELAY', 1000),
maxConcurrentRequests: getEnvNumber('HTTP_CLIENT_MAX_CONCURRENT', 10),
healthCheckInterval: getEnvNumber('HTTP_CLIENT_HEALTH_CHECK_INTERVAL', 30000)
},
session: {
timeoutMinutes: getEnvNumber('SESSION_TIMEOUT_MINUTES', 60),
cleanupIntervalMinutes: getEnvNumber('SESSION_CLEANUP_INTERVAL_MINUTES', 5),
maxConcurrentSessions: getEnvNumber('MAX_CONCURRENT_SESSIONS', 100)
},
logging: {
level: getEnvVar('LOG_LEVEL', 'info'),
file: getEnvVar('LOG_FILE_PATH', './logs/middleware.log'),
maxFiles: getEnvNumber('LOG_MAX_FILES', 5),
maxSize: getEnvVar('LOG_MAX_SIZE', '10m')
},
cors: {
origin: getEnvVar('CORS_ORIGIN', '*')
},
database: {
mainUri: getEnvVar('MAIN_DB_URI', '', false), // Optional for npm package
mcpUri: getEnvVar('MCP_DB_URI', '', false), // Optional for npm package
mongoUri: getEnvVar('MONGODB_URI', '', false), // Legacy support
options: {
maxPoolSize: getEnvNumber('DB_MAX_POOL_SIZE', 10),
serverSelectionTimeoutMS: getEnvNumber('DB_SERVER_SELECTION_TIMEOUT', 5000),
socketTimeoutMS: getEnvNumber('DB_SOCKET_TIMEOUT', 45000)
}
},
apiKeys: resolveApiKeys()
};
// š Configuration reporting
export const getConfigSummary = () => {
const userKeyCount = Object.keys(config.apiKeys.user).length;
const tryaiiKeyCount = Object.keys(config.apiKeys.tryaii).length;
return `
šļø TryAII MCP Server Configuration:
āā Server: ${config.server.host}:${config.server.port} (${config.server.nodeEnv})
āā Modes: HTTP=${config.server.modes.http}, MCP=${config.server.modes.mcp}${config.server.modes.mcpPort ? `:${config.server.modes.mcpPort}` : ''}
āā Session: ${config.session.timeoutMinutes}min timeout, ${config.session.maxConcurrentSessions} max concurrent
āā User Keys: ${userKeyCount > 0 ? `${userKeyCount} configured (${Object.keys(config.apiKeys.user).join(', ')})` : 'None - will use TryAII fallbacks'}
āā TryAII Keys: ${tryaiiKeyCount > 0 ? `${tryaiiKeyCount} available` : 'None configured'}
āā Database: Main=${config.database.mainUri.includes('localhost') ? 'Local' : 'Atlas'}, MCP=${config.database.mcpUri.includes('localhost') ? 'Local' : 'Atlas'}`;
};
// Validation
export const validateConfig = () => {
// Check MCP executable (only required if MCP mode is enabled)
if (config.server.modes.mcp && !config.mcp.executablePath) {
console.warn('ā ļø MCP mode enabled but no executable path set. Disabling MCP mode.');
config.server.modes.mcp = false;
}
// Check database URIs (warn if missing in production, don't crash)
if (process.env.NODE_ENV === 'production') {
if (!config.database.mainUri) {
console.warn('ā ļø MAIN_DB_URI not configured. Database features will be disabled.');
}
if (!config.database.mcpUri) {
console.warn('ā ļø MCP_DB_URI not configured. MCP session storage will be disabled.');
}
}
// Validate numeric ranges
if (config.server.port < 1 || config.server.port > 65535) {
throw new Error('Server port must be between 1 and 65535');
}
if (config.session.timeoutMinutes < 1) {
throw new Error('Session timeout must be at least 1 minute');
}
// Check API key availability
const totalAvailableKeys = Object.keys(config.apiKeys.user).length + Object.keys(config.apiKeys.tryaii).length;
if (totalAvailableKeys === 0) {
console.warn('ā ļø No API keys configured. The MCP server may not function properly.');
}
// Debug: Show what we loaded
console.log('š§ Configuration loaded:');
console.log(` Port: ${config.server.port}`);
console.log(` MCP Mode: ${config.server.modes.mcp}`);
console.log(` HTTP Mode: ${config.server.modes.http}`);
console.log(` Database Main: ${config.database.mainUri ? 'Configured' : 'Missing'}`);
console.log(` Database MCP: ${config.database.mcpUri ? 'Configured' : 'Missing'}`);
console.log(` API Keys: ${totalAvailableKeys} total`);
console.log(` Environment: ${config.server.nodeEnv}`);
// Only show config summary in development
if (process.env.NODE_ENV === 'development') {
console.log(getConfigSummary());
}
};
// Don't auto-validate - let the main application call validateConfig() when needed
//# sourceMappingURL=config.js.map