Files
Momento/mcp-server/config.js
Antigravity 0784c94242
Some checks failed
CI / Lint, Test & Build (push) Failing after 57s
CI / Deploy production (on server) (push) Has been skipped
feat(notes): vues structurées tableau/kanban, flashcards et MCP robuste
Ajoute la base organisable par carnet (schéma, champs partagés, valeurs par note)
avec activation guidée, tableau éditable, kanban et suppression de colonnes.
Corrige le multiselect en vue tableau et enrichit sidebar, grille et i18n FR/EN.
Inclut aussi les améliorations flashcards SM-2, l'audit consentement IA et la
robustesse du serveur MCP (config, validation, rate-limit, métriques).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 23:03:16 +00:00

362 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
const portValidation = validatePort(config.port);
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;