bc-webclient-mcp
Version:
Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central via WebUI protocol. Enables AI assistants to interact with BC through the web client protocol, supporting Card, List, and Document pages with full line item support and server
206 lines • 6.61 kB
JavaScript
/**
* Centralized Configuration Module
*
* Type-safe environment variable management for the BC MCP Server.
* All environment variables should be accessed through this module.
*/
import * as dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
/**
* Parse and validate environment variables
*/
class Config {
config;
constructor() {
this.config = this.parseEnvironment();
this.validateConfig();
}
/**
* Parse environment variables with defaults
*/
parseEnvironment() {
return {
nodeEnv: this.getNodeEnv(),
logLevel: this.getLogLevel(),
bc: {
baseUrl: process.env.BC_BASE_URL || 'http://Cronus27/BC',
username: process.env.BC_USERNAME || 'sshadows',
password: process.env.BC_PASSWORD || '1234',
tenantId: process.env.BC_TENANT_ID || 'default',
timeout: this.getNumberEnv('BC_TIMEOUT', 120000),
searchTimingWindowMs: this.getNumberEnv('BC_SEARCH_TIMING_WINDOW_MS', 15000),
},
debug: this.parseDebugConfig(),
};
}
/**
* Parse debug logging configuration
*/
parseDebugConfig() {
const enabled = this.getBooleanEnv('DEBUG_MODE', false);
const channelsStr = process.env.DEBUG_CHANNELS || 'all';
const channelsList = channelsStr.split(',').map((c) => c.trim());
// If 'all' is specified, enable all channels (except 'all' itself)
const channels = new Set(channelsList);
if (channels.has('all')) {
return {
enabled,
logDir: process.env.DEBUG_LOG_DIR || '.debug-logs',
maxSizeMB: this.getNumberEnv('DEBUG_LOG_MAX_SIZE_MB', 50),
maxFiles: this.getNumberEnv('DEBUG_LOG_MAX_FILES', 10),
channels: new Set(['stdio', 'tools', 'websocket', 'handlers', 'session', 'cache']),
logFullHandlers: this.getBooleanEnv('DEBUG_LOG_FULL_HANDLERS', false),
logFullWsMessages: this.getBooleanEnv('DEBUG_LOG_FULL_WS_MESSAGES', true),
};
}
return {
enabled,
logDir: process.env.DEBUG_LOG_DIR || '.debug-logs',
maxSizeMB: this.getNumberEnv('DEBUG_LOG_MAX_SIZE_MB', 50),
maxFiles: this.getNumberEnv('DEBUG_LOG_MAX_FILES', 10),
channels,
logFullHandlers: this.getBooleanEnv('DEBUG_LOG_FULL_HANDLERS', false),
logFullWsMessages: this.getBooleanEnv('DEBUG_LOG_FULL_WS_MESSAGES', true),
};
}
/**
* Get Node environment with validation
*/
getNodeEnv() {
const env = process.env.NODE_ENV?.toLowerCase();
if (env === 'production' || env === 'test') {
return env;
}
return 'development';
}
/**
* Get log level with validation
*/
getLogLevel() {
const level = process.env.LOG_LEVEL?.toLowerCase();
if (level === 'debug' || level === 'info' || level === 'warn' || level === 'error') {
return level;
}
return 'info';
}
/**
* Get numeric environment variable with default
*/
getNumberEnv(key, defaultValue) {
const value = process.env[key];
if (!value) {
return defaultValue;
}
const parsed = parseInt(value, 10);
if (isNaN(parsed)) {
console.error(`Invalid number for ${key}="${value}", using default: ${defaultValue}`);
return defaultValue;
}
return parsed;
}
/**
* Get boolean environment variable with default
*/
getBooleanEnv(key, defaultValue) {
const value = process.env[key];
if (!value) {
return defaultValue;
}
return value.toLowerCase() === 'true' || value === '1';
}
/**
* Validate configuration
*/
validateConfig() {
// Validate BC configuration
if (!this.config.bc.baseUrl) {
throw new Error('BC_BASE_URL is required');
}
if (!this.config.bc.username) {
throw new Error('BC_USERNAME is required');
}
// Password can be empty for some environments
if (this.config.bc.password === '') {
console.error('BC_PASSWORD is empty - authentication may fail');
}
// Validate URL format
try {
new URL(this.config.bc.baseUrl);
}
catch (error) {
throw new Error(`Invalid BC_BASE_URL: ${this.config.bc.baseUrl}`);
}
// Log configuration (without sensitive data) - use console.error to write to stderr for MCP stdio compatibility
if (process.env.NODE_ENV !== 'test') {
console.error('Configuration loaded:', {
nodeEnv: this.config.nodeEnv,
logLevel: this.config.logLevel,
bcBaseUrl: this.config.bc.baseUrl,
bcUsername: this.config.bc.username,
bcTenantId: this.config.bc.tenantId,
bcTimeout: this.config.bc.timeout,
});
}
}
/**
* Get the full configuration
*/
getConfig() {
return this.config;
}
/**
* Get Node environment
*/
get nodeEnv() {
return this.config.nodeEnv;
}
/**
* Get log level
*/
get logLevel() {
return this.config.logLevel;
}
/**
* Get BC configuration
*/
get bc() {
return this.config.bc;
}
/**
* Get debug configuration
*/
get debug() {
return this.config.debug;
}
/**
* Check if running in development mode
*/
get isDevelopment() {
return this.config.nodeEnv === 'development';
}
/**
* Check if running in production mode
*/
get isProduction() {
return this.config.nodeEnv === 'production';
}
/**
* Check if running in test mode
*/
get isTest() {
return this.config.nodeEnv === 'test';
}
}
// Create singleton instance
const configInstance = new Config();
// Export the singleton
export const config = configInstance;
// Export convenience accessors
export const bcConfig = configInstance.bc;
export const isDevelopment = configInstance.isDevelopment;
export const isProduction = configInstance.isProduction;
export const isTest = configInstance.isTest;
export const nodeEnv = configInstance.nodeEnv;
export const logLevel = configInstance.logLevel;
//# sourceMappingURL=config.js.map