Skip to main content
Glama
connection-manager.js8.73 kB
/** * Database Connection Manager * * Handles SQL Server connection pooling, retry logic, and authentication * Extracted from the main SqlServerMCP class for better organization */ import sql from 'mssql'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { ServerConfig } from '../config/server-config.js'; export class ConnectionManager { constructor(config = {}) { // Use ServerConfig for centralized configuration management this.serverConfig = config.serverConfig || new ServerConfig(); // Configuration with defaults (prefer passed config, fallback to ServerConfig, then env vars) const connectionConfig = this.serverConfig.getConnectionConfig(); this.connectionTimeout = config.connectionTimeout || connectionConfig.connectionTimeout; this.requestTimeout = config.requestTimeout || connectionConfig.requestTimeout; this.maxRetries = config.maxRetries || connectionConfig.maxRetries; this.retryDelay = config.retryDelay || connectionConfig.retryDelay; // Connection state this.pool = null; this.isConnected = false; } /** * Safely parse integer from environment variable with validation * @param {string} envVar - Environment variable value * @param {string} defaultValue - Default value as string * @param {number} min - Minimum allowed value * @param {number} max - Maximum allowed value * @returns {number} Parsed integer value * @private */ _safeParseInt(envVar, defaultValue, min = 0, max = Number.MAX_SAFE_INTEGER) { const value = parseInt(envVar || defaultValue); if (isNaN(value) || value < min || value > max) { console.warn(`Invalid integer value: ${envVar}, using default: ${defaultValue}`); return parseInt(defaultValue); } return value; } /** * Connect to SQL Server with retry logic and proper authentication handling * @returns {Promise<Object>} SQL Server connection pool */ async connect() { if (this.pool && this.pool.connected) { // Connection pool already available - reusing efficiently return this.pool; } if (process.env.NODE_ENV !== 'test' && process.env.SQL_SERVER_DEBUG === 'true') { console.error('Establishing new database connection...'); } const baseConfig = this._buildConnectionConfig(); // Retry logic with exponential backoff let attempt = 0; let lastError = null; while (attempt < this.maxRetries) { try { attempt += 1; const config = { ...baseConfig }; this.pool = await sql.connect(config); this.isConnected = true; if (process.env.NODE_ENV !== 'test' && process.env.SQL_SERVER_DEBUG === 'true') { console.error(`Connected to SQL Server successfully (attempt ${attempt})`); } return this.pool; } catch (error) { lastError = error; if (process.env.NODE_ENV !== 'test' && process.env.SQL_SERVER_DEBUG === 'true') { console.error(`Connection attempt ${attempt} failed: ${error.message}`); } if (attempt >= this.maxRetries) break; const delay = this.retryDelay * Math.pow(2, attempt - 1); await new Promise(res => setTimeout(res, delay)); } } throw new McpError( ErrorCode.InternalError, `Failed to connect to SQL Server after ${this.maxRetries} attempts: ${lastError ? lastError.message : 'Unknown error'}` ); } /** * Determine if we're in a development-like environment (delegates to ServerConfig) * @private * @returns {boolean} True if environment appears to be development */ _isLikelyDevEnvironment() { return this.serverConfig._isLikelyDevEnvironment(); } /** * Build the connection configuration from environment variables * @private * @returns {Object} SQL Server connection configuration */ _buildConnectionConfig() { const isDevEnv = this._isLikelyDevEnvironment(); // Context-aware SSL certificate trust: // - Explicit 'true': Always trust (developer override) // - Explicit 'false': Never trust (production override) // - Not set: Smart default based on environment (trust in dev, don't trust in prod) let trustServerCertificate; if (process.env.SQL_SERVER_TRUST_CERT === 'true') { trustServerCertificate = true; } else if (process.env.SQL_SERVER_TRUST_CERT === 'false') { trustServerCertificate = false; } else { // Environment-aware default: trust certificates in development environments trustServerCertificate = isDevEnv; } const baseConfig = { server: process.env.SQL_SERVER_HOST || 'localhost', port: this._safeParseInt(process.env.SQL_SERVER_PORT, '1433', 1, 65535), database: process.env.SQL_SERVER_DATABASE || 'master', user: process.env.SQL_SERVER_USER, password: process.env.SQL_SERVER_PASSWORD, options: { encrypt: process.env.SQL_SERVER_ENCRYPT !== 'false', trustServerCertificate, enableArithAbort: true, requestTimeout: this.requestTimeout }, connectionTimeout: this.connectionTimeout, requestTimeout: this.requestTimeout, pool: { max: this._safeParseInt(process.env.SQL_SERVER_POOL_MAX, '10', 1, 100), min: this._safeParseInt(process.env.SQL_SERVER_POOL_MIN, '0', 0, 50), idleTimeoutMillis: this._safeParseInt( process.env.SQL_SERVER_POOL_IDLE_TIMEOUT_MS, '30000', 1000, 300000 ) } }; // Handle Windows Authentication if no user/password provided if (!baseConfig.user && !baseConfig.password) { baseConfig.authentication = { type: 'ntlm', options: { domain: process.env.SQL_SERVER_DOMAIN || '' } }; // Remove user/password for Windows auth delete baseConfig.user; delete baseConfig.password; } else { // Ensure we don't mix SQL Server auth with NTLM delete baseConfig.authentication; } return baseConfig; } /** * Get the current connection pool * @returns {Object|null} Current connection pool or null */ getPool() { return this.pool; } /** * Check if currently connected * @returns {boolean} Connection status */ isConnectionActive() { return this.isConnected && this.pool && this.pool.connected; } /** * Close the connection pool * @returns {Promise<void>} */ async close() { if (this.pool) { try { await this.pool.close(); } catch (error) { // Log the error but don't throw - we still want to null out the pool if (process.env.NODE_ENV !== 'test') { console.error('Error closing connection pool:', error.message); } } finally { this.pool = null; this.isConnected = false; } } } /** * Get connection health information * @returns {Object} Connection health metrics */ getConnectionHealth() { if (!this.pool) { return { connected: false, status: 'No connection pool' }; } const health = { connected: this.pool.connected, connecting: this.pool.connecting, healthy: this.pool.healthy, status: this.pool.connected ? 'Connected' : 'Disconnected', pool: { size: this.pool.size, available: this.pool.available, pending: this.pool.pending, borrowed: this.pool.borrowed } }; // Try to extract SSL/TLS certificate information if encryption is enabled if (this.pool.connected && process.env.SQL_SERVER_ENCRYPT === 'true') { health.ssl = this._extractSSLInfo(); } return health; } /** * Get SSL/TLS connection information * @private * @returns {Object|null} SSL connection status or null if not available */ _extractSSLInfo() { try { if (!this.pool || !this.pool.connected) return null; // Since mssql library doesn't expose underlying TLS socket details easily, // provide connection security information based on configuration const config = this._buildConnectionConfig(); return { enabled: true, encrypt: config.options.encrypt, trust_server_certificate: config.options.trustServerCertificate, connection_status: 'Encrypted connection established', server: `${config.server}:${config.port}`, protocol: 'TLS/SSL', note: 'Certificate details not available through mssql library abstraction' }; } catch (error) { if (process.env.NODE_ENV !== 'test') { console.error('Could not extract SSL connection info:', error.message); } } return null; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/egarcia74/warp-sql-server-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server