Files
Momento/mcp-server/config.js
Antigravity 80ccc1f6de
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 7m14s
CI / Deploy production (on server) (push) Successful in 1m25s
feat: dashboard Second Brain, essai 7 jours et vérification e-mail
Rendre le dashboard actionnable (inbox, peek, carte mentale), aligner la facturation sur l’essai 7 jours, et bloquer le login e-mail tant que l’adresse n’est pas confirmée.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 07:19:36 +00:00

363 lines
11 KiB
JavaScript

/**
* Memento MCP Server - Configuration Management
*
* Centralized configuration with validation and defaults.
* Validates all required environment variables on startup.
*/
/**
* Parse boolean from string or value
*/
function parseBoolean(value, defaultValue = false) {
if (value === undefined || value === null || value === '') {
return defaultValue;
}
if (typeof value === 'boolean') return value;
const str = String(value).toLowerCase();
return ['true', '1', 'yes', 'on'].includes(str);
}
/**
* Parse integer with default
*/
function parseInt(value, defaultValue, min, max) {
if (value === undefined || value === null || value === '') {
return defaultValue;
}
const parsed = Number.parseInt(value, 10);
if (Number.isNaN(parsed)) return defaultValue;
if (min !== undefined && parsed < min) return min;
if (max !== undefined && parsed > max) return max;
return parsed;
}
/**
* Parse array from comma-separated string
*/
function parseArray(value, defaultValue = []) {
if (!value) return defaultValue;
if (Array.isArray(value)) return value;
return String(value).split(',').map((s) => s.trim()).filter(Boolean);
}
/**
* Get environment variable with fallback
*/
function env(key, fallback = '') {
return process.env[key] || fallback;
}
/**
* Validate database URL format
*/
function validateDatabaseUrl(url) {
if (!url) {
return { valid: false, error: 'DATABASE_URL is required' };
}
const isValid =
url.startsWith('postgresql://') ||
url.startsWith('postgres://') ||
url.startsWith('file:') ||
url.includes('.db') ||
url.includes('.sqlite');
if (!isValid) {
return {
valid: false,
error: 'DATABASE_URL must be a valid PostgreSQL or SQLite connection string',
};
}
return { valid: true };
}
/**
* Validate port number
*/
function validatePort(port) {
if (port < 1 || port > 65535) {
return { valid: false, error: `PORT must be between 1 and 65535, got ${port}` };
}
return { valid: true };
}
/**
* Validate log level
*/
function validateLogLevel(level) {
const validLevels = ['debug', 'info', 'warn', 'error', 'silent'];
if (!validLevels.includes(level)) {
return {
valid: false,
error: `LOG_LEVEL must be one of: ${validLevels.join(', ')}`,
};
}
return { valid: true };
}
/**
* Validate timeout values
*/
function validateTimeout(timeout, name) {
const min = 1000; // 1 second minimum
const max = 300000; // 5 minutes maximum
if (timeout < min || timeout > max) {
return {
valid: false,
error: `${name} must be between ${min}ms and ${max}ms, got ${timeout}ms`,
};
}
return { valid: true };
}
/**
* Configuration object with validation
*/
export const config = {
// Server
port: parseInt(env('PORT', '3001'), 3001, 1, 65535),
nodeEnv: env('NODE_ENV', 'development'),
// Database
databaseUrl: env('DATABASE_URL', ''),
isPostgres:
env('DATABASE_URL', '').startsWith('postgresql://') ||
env('DATABASE_URL', '').startsWith('postgres://'),
connectionLimit: parseInt(env('DB_CONNECTION_LIMIT', '10'), 10, 1, 100),
poolTimeout: parseInt(env('DB_POOL_TIMEOUT', '10'), 10, 1, 60),
// Application
appBaseUrl: env('APP_BASE_URL', 'http://localhost:3000'),
userId: env('USER_ID', null), // Optional user filter
// Authentication
requireAuth: parseBoolean(env('MCP_REQUIRE_AUTH'), false),
staticApiKey: env('MCP_API_KEY', null),
// Logging
logLevel: env('MCP_LOG_LEVEL', 'info').toLowerCase(),
logToFile: parseBoolean(env('MCP_LOG_TO_FILE'), false),
// Performance
requestTimeout: parseInt(env('MCP_REQUEST_TIMEOUT', '30000'), 30000, 1000, 300000),
rateLimit: parseInt(env('MCP_RATE_LIMIT', '100'), 100, 1, 10000),
rateLimitWindow: parseInt(env('MCP_RATE_LIMIT_WINDOW', '60000'), 60000, 1000, 3600000),
// Session management
maxSessions: parseInt(env('MCP_MAX_SESSIONS', '500'), 500, 10, 10000),
sessionTtl: parseInt(env('MCP_SESSION_TTL', '3600000'), 3600000, 60000, 86400000),
sessionCleanupInterval: parseInt(env('MCP_SESSION_CLEANUP_INTERVAL', '300000'), 300000, 60000, 3600000),
// Caching
enableCache: parseBoolean(env('MCP_ENABLE_CACHE'), true),
cacheTtl: parseInt(env('MCP_CACHE_TTL', '60000'), 60000, 0, 3600000),
cacheMaxSize: parseInt(env('MCP_CACHE_MAX_SIZE', '1000'), 1000, 100, 10000),
// Features
enableMetrics: parseBoolean(env('MCP_ENABLE_METRICS'), true),
enableAuditLog: parseBoolean(env('MCP_ENABLE_AUDIT_LOG'), true),
enableTools: parseArray(env('MCP_ENABLE_TOOLS'), null), // null = all tools enabled
disableTools: parseArray(env('MCP_DISABLE_TOOLS'), []),
// Security
maxRequestSize: parseInt(env('MCP_MAX_REQUEST_SIZE', '10485760'), 10485760, 1024, 104857600), // 10MB default
maxResponseSize: parseInt(env('MCP_MAX_RESPONSE_SIZE', '52428800'), 52428800, 1024, 524288000), // 50MB default
allowedOrigins: parseArray(env('MCP_ALLOWED_ORIGINS'), '*'),
// Observability
metricsPath: env('MCP_METRICS_PATH', '/metrics'),
healthPath: env('MCP_HEALTH_PATH', '/health'),
debugPath: env('MCP_DEBUG_PATH', '/debug'),
// Timeouts
databaseQueryTimeout: parseInt(env('MCP_DB_QUERY_TIMEOUT', '30000'), 30000, 1000, 120000),
toolExecutionTimeout: parseInt(env('MCP_TOOL_TIMEOUT', '60000'), 60000, 5000, 300000),
};
/**
* Validate all configuration values
* Returns array of validation errors (empty if valid)
*/
export function validateConfig() {
const errors = [];
// Required fields
if (!config.databaseUrl) {
errors.push({
key: 'DATABASE_URL',
message: 'DATABASE_URL is required',
critical: true,
});
} else {
const dbValidation = validateDatabaseUrl(config.databaseUrl);
if (!dbValidation.valid) {
errors.push({ key: 'DATABASE_URL', message: dbValidation.error, critical: true });
}
}
// Port validation (check raw value before clamping)
const rawPort = Number.parseInt(env('PORT', '3001'), 10);
const portValidation = validatePort(Number.isNaN(rawPort) ? config.port : rawPort);
if (!portValidation.valid) {
errors.push({ key: 'PORT', message: portValidation.error, critical: true });
}
// Log level validation
const logLevelValidation = validateLogLevel(config.logLevel);
if (!logLevelValidation.valid) {
errors.push({ key: 'MCP_LOG_LEVEL', message: logLevelValidation.error, critical: false });
}
// Timeout validations
const requestTimeoutValidation = validateTimeout(config.requestTimeout, 'REQUEST_TIMEOUT');
if (!requestTimeoutValidation.valid) {
errors.push({
key: 'MCP_REQUEST_TIMEOUT',
message: requestTimeoutValidation.error,
critical: false,
});
}
const dbTimeoutValidation = validateTimeout(config.databaseQueryTimeout, 'DB_QUERY_TIMEOUT');
if (!dbTimeoutValidation.valid) {
errors.push({
key: 'MCP_DB_QUERY_TIMEOUT',
message: dbTimeoutValidation.error,
critical: false,
});
}
// Auth configuration
if (config.requireAuth && !config.staticApiKey) {
// Warning: auth required but no static key set
// This is OK - we'll use database-stored keys
// Just log a warning in development
if (config.nodeEnv === 'development') {
errors.push({
key: 'MCP_REQUIRE_AUTH',
message: 'Auth is required but no MCP_API_KEY is set. Database API keys will be used.',
critical: false,
level: 'warning',
});
}
}
// Check for conflicting tool enable/disable
if (config.enableTools && config.disableTools.length > 0) {
const conflicts = config.enableTools.filter((t) => config.disableTools.includes(t));
if (conflicts.length > 0) {
errors.push({
key: 'MCP_ENABLE_TOOLS / MCP_DISABLE_TOOLS',
message: `Tools both enabled and disabled: ${conflicts.join(', ')}. Disabled takes precedence.`,
critical: false,
level: 'warning',
});
}
}
return errors;
}
/**
* Get configuration for display (sanitized)
* Removes sensitive values like API keys and database URLs
*/
export function getPublicConfig() {
return {
port: config.port,
nodeEnv: config.nodeEnv,
isPostgres: config.isPostgres,
appBaseUrl: config.appBaseUrl,
userId: config.userId || null,
requireAuth: config.requireAuth,
hasStaticKey: Boolean(config.staticApiKey),
logLevel: config.logLevel,
requestTimeout: config.requestTimeout,
rateLimit: config.rateLimit,
maxSessions: config.maxSessions,
sessionTtl: config.sessionTtl,
enableCache: config.enableCache,
cacheTtl: config.cacheTtl,
enableMetrics: config.enableMetrics,
enableAuditLog: config.enableAuditLog,
enabledToolCount: config.enableTools?.length || 'all',
disabledToolCount: config.disableTools.length,
maxRequestSize: config.maxRequestSize,
allowedOrigins: config.allowedOrigins,
};
}
/**
* Get database URL for logging (sanitized)
*/
export function getSafeDatabaseUrl() {
if (!config.databaseUrl) return '<not set>';
try {
const url = new URL(config.databaseUrl);
// Mask password
if (url.password) {
url.password = '***';
}
return url.toString();
} catch {
// If not a valid URL, return partially masked version
const url = config.databaseUrl;
if (url.includes(':')) {
const parts = url.split('@');
if (parts.length > 1) {
return `***@${parts[1]}`;
}
}
return url.substring(0, 20) + '...';
}
}
/**
* Print configuration on startup
*/
export function printConfig() {
const errors = validateConfig();
console.log(`
╔═══════════════════════════════════════════════════════╗
║ Memento MCP Server Configuration ║
╚═══════════════════════════════════════════════════════╝
Environment: ${config.nodeEnv.toUpperCase()}
Port: ${config.port}
Database: ${config.isPostgres ? 'PostgreSQL' : 'SQLite'}
Database URL: ${getSafeDatabaseUrl()}
Authentication: ${config.requireAuth ? 'ENABLED' : 'DISABLED'}
Static Key: ${config.staticApiKey ? 'SET' : 'NOT SET'}
Rate Limit: ${config.rateLimit} requests / ${config.rateLimitWindow}ms
Sessions:
Max: ${config.maxSessions}
TTL: ${config.sessionTtl}ms
Cleanup: ${config.sessionCleanupInterval}ms
Timeouts:
Request: ${config.requestTimeout}ms
DB Query: ${config.databaseQueryTimeout}ms
Tool: ${config.toolExecutionTimeout}ms
Cache: ${config.enableCache ? 'ENABLED' : 'DISABLED'}
TTL: ${config.cacheTtl}ms
Max Size: ${config.cacheMaxSize}
Features:
Metrics: ${config.enableMetrics ? 'ENABLED' : 'DISABLED'}
Audit Log: ${config.enableAuditLog ? 'ENABLED' : 'DISABLED'}
${errors.length > 0 ? `⚠️ CONFIGURATION WARNINGS/ERRORS:
${errors.map((e) => ` ${e.critical ? '❌' : '⚠️'} ${e.key}: ${e.message}`).join('\n')}
` : ''}
`);
}
export default config;