/**
* Configuration management with Zod validation
*
* Environment variables are prefixed with MCP_SERVER_ to avoid conflicts.
* All configuration is validated at startup for fail-fast behavior.
*/
import { z } from 'zod';
import dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
/**
* Configuration schema with defaults and validation
*/
const ConfigSchema = z.object({
// Server identification
serverName: z.string().default('mcp-server-template'),
serverVersion: z.string().default('0.1.0'),
// Logging
logLevel: z
.enum(['debug', 'info', 'notice', 'warning', 'error', 'critical'])
.default('info'),
logToFile: z.boolean().default(false),
logDir: z.string().default('.logs'),
// Database/Cache
dbPath: z.string().default('.data/cache.db'),
cacheEnabled: z.boolean().default(true),
cacheTtlSeconds: z.number().positive().default(3600), // 1 hour default
// Timeouts
requestTimeoutMs: z.number().positive().default(30000),
// Observability
sentryDsn: z.string().optional(),
otelEnabled: z.boolean().default(false),
otelEndpoint: z.string().optional(),
// Feature flags
debugMode: z.boolean().default(false),
});
export type Config = z.infer<typeof ConfigSchema>;
/**
* Parse environment variables into typed configuration
*/
function parseEnvConfig(): Config {
const rawConfig = {
serverName: process.env['MCP_SERVER_NAME'],
serverVersion: process.env['MCP_SERVER_VERSION'],
logLevel: process.env['MCP_SERVER_LOG_LEVEL'],
logToFile: process.env['MCP_SERVER_LOG_TO_FILE'] === 'true',
logDir: process.env['MCP_SERVER_LOG_DIR'],
dbPath: process.env['MCP_SERVER_DB_PATH'],
cacheEnabled: process.env['MCP_SERVER_CACHE_ENABLED'] !== 'false',
cacheTtlSeconds: process.env['MCP_SERVER_CACHE_TTL']
? parseInt(process.env['MCP_SERVER_CACHE_TTL'], 10)
: undefined,
requestTimeoutMs: process.env['MCP_SERVER_TIMEOUT']
? parseInt(process.env['MCP_SERVER_TIMEOUT'], 10)
: undefined,
sentryDsn: process.env['MCP_SERVER_SENTRY_DSN'],
otelEnabled: process.env['OTEL_ENABLED'] === 'true',
otelEndpoint: process.env['OTEL_EXPORTER_OTLP_ENDPOINT'],
debugMode: process.env['MCP_SERVER_DEBUG'] === 'true',
};
// Remove undefined values so defaults apply
const cleanConfig = Object.fromEntries(
Object.entries(rawConfig).filter(([_, v]) => v !== undefined)
);
return ConfigSchema.parse(cleanConfig);
}
// Cached configuration singleton
let cachedConfig: Config | null = null;
/**
* Get validated configuration (cached after first call)
*/
export function getConfig(): Config {
if (!cachedConfig) {
cachedConfig = parseEnvConfig();
}
return cachedConfig;
}
/**
* Reset configuration cache (useful for testing)
*/
export function resetConfig(): void {
cachedConfig = null;
}
/**
* Validate configuration and return any errors
*/
export function validateConfig(): { valid: boolean; errors?: string[] } {
try {
parseEnvConfig();
return { valid: true };
} catch (error) {
if (error instanceof z.ZodError) {
return {
valid: false,
errors: error.errors.map((e) => `${e.path.join('.')}: ${e.message}`),
};
}
return { valid: false, errors: ['Unknown configuration error'] };
}
}