UNPKG

crew-management-mcp-server

Version:

Crew management server handling crew records, certifications, scheduling, payroll, and vessel assignments with ERP access for data extraction

280 lines 12.3 kB
import { getConfig } from './config.js'; import { logger } from './logger.js'; // Snowflake connection pool let connectionPool = []; const MAX_POOL_SIZE = 5; export class SnowflakeClient { constructor() { this.connection = null; } async connect() { logger.info('Connecting to Snowflake database...'); try { // Get fresh configuration const config = getConfig(); // Validate required configuration if (!config.snowflakeAccount || !config.snowflakeUser || !config.snowflakePassword) { throw new Error('Missing required Snowflake configuration: account, user, or password'); } // Dynamic import for ES module compatibility const snowflake = await import('snowflake-sdk'); const sdk = snowflake.default || snowflake; const connectionOptions = { account: config.snowflakeAccount, username: config.snowflakeUser, // Note: SDK uses 'username', not 'user' password: config.snowflakePassword, application: 'crew-management-mcp-server', timeout: 60000, // 60 seconds // Add optional parameters only if they exist ...(config.snowflakeWarehouse && { warehouse: config.snowflakeWarehouse }), ...(config.snowflakeDatabase && { database: config.snowflakeDatabase }), ...(config.snowflakeSchema && { schema: config.snowflakeSchema }), ...(config.snowflakeRole && { role: config.snowflakeRole }), }; return new Promise((resolve, reject) => { try { // Configure Snowflake SDK sdk.configure({ logLevel: config.logLevel === 'debug' ? 'DEBUG' : 'INFO', // Remove ocspFailOpen for compatibility }); logger.info('Creating Snowflake connection with options:', { account: connectionOptions.account, username: connectionOptions.username, warehouse: connectionOptions.warehouse, database: connectionOptions.database, schema: connectionOptions.schema }); const connection = sdk.createConnection(connectionOptions); connection.connect((err, conn) => { if (err) { logger.error('Unable to connect to Snowflake:', { code: err.code, message: err.message, sqlState: err.sqlState, data: err.data }); reject(err); } else { logger.info('Successfully connected to Snowflake', { sessionId: conn.getSessionId?.(), database: conn.getDatabase?.(), schema: conn.getSchema?.(), warehouse: conn.getWarehouse?.() }); this.connection = conn; resolve(conn); } }); } catch (setupError) { logger.error('Error setting up Snowflake connection:', setupError); reject(setupError); } }); } catch (error) { logger.error('Error connecting to Snowflake:', error); throw error; } } async execute(query) { if (!this.connection) { await this.connect(); } return new Promise((resolve, reject) => { // Add timeout to prevent hanging const timeoutId = setTimeout(() => { logger.error('Snowflake query execution timeout after 30 seconds'); reject(new Error('Query execution timeout')); }, 30000); this.connection.execute({ sqlText: query, complete: (err, stmt, rows) => { clearTimeout(timeoutId); if (err) { logger.error('Error executing Snowflake query:', { code: err.code, message: err.message, sqlState: err.sqlState, query: query.substring(0, 200) + '...' }); reject(err); } else { try { // Extract column names more robustly let columns = []; try { const columnInfo = stmt.getColumns(); columns = columnInfo.map((col) => { // Try different ways to get column name return col.name || col.columnName || col.COLUMN_NAME || col.getName?.() || 'UNKNOWN_COLUMN'; }); logger.debug('Column extraction successful', { columnCount: columns.length, columnNames: columns.slice(0, 5) // Log first 5 column names }); } catch (columnError) { logger.warn('Error extracting column names, using fallback', columnError); // Fallback: try to infer from first row if available if (rows && rows.length > 0 && typeof rows[0] === 'object') { columns = Object.keys(rows[0]); logger.debug('Using fallback column extraction from row data', { columns: columns.slice(0, 5) }); } } logger.debug('Snowflake query executed successfully', { query: query.substring(0, 100) + '...', rowCount: rows?.length || 0, columnCount: columns.length, hasColumns: columns.length > 0, rowType: rows && rows.length > 0 ? typeof rows[0] : 'no data' }); // Rows are already objects from Snowflake SDK by default resolve({ columns, rows: rows || [] }); } catch (resultError) { logger.error('Error processing Snowflake query results:', resultError); reject(resultError); } } } }); }); } async close() { if (this.connection) { return new Promise((resolve, reject) => { this.connection.destroy((err, conn) => { if (err) { logger.error('Error closing Snowflake connection:', err); reject(err); } else { logger.info('Snowflake connection closed successfully'); this.connection = null; resolve(); } }); }); } } } export async function getConnectionFromPool() { // Simple connection pooling if (connectionPool.length > 0) { return connectionPool.pop(); } const client = new SnowflakeClient(); await client.connect(); return client; } export async function returnConnectionToPool(client) { if (connectionPool.length < MAX_POOL_SIZE) { connectionPool.push(client); } else { await client.close(); } } export async function closeAllConnections() { const closePromises = connectionPool.map(client => client.close()); await Promise.all(closePromises); connectionPool = []; logger.info('All Snowflake connections closed'); } export async function testConnection() { try { logger.info('Starting Snowflake connection test...'); const client = new SnowflakeClient(); logger.info('Establishing connection...'); await client.connect(); logger.info('Executing test query...'); const result = await client.execute('SELECT CURRENT_VERSION() as version'); logger.info('Snowflake connection test successful', { version: result.rows[0]?.[0], rowCount: result.rows.length }); logger.info('Closing test connection...'); await client.close(); return true; } catch (error) { logger.error('Snowflake connection test failed:', { message: error instanceof Error ? error.message : 'Unknown error', code: error?.code, sqlState: error?.sqlState }); return false; } } export async function getSeafarerDetailsFromSnowflake(crewIds, requiredFields) { const allResults = []; if (!crewIds || crewIds.length === 0) { logger.warn('No crew IDs provided for Snowflake query'); return allResults; } try { const client = await getConnectionFromPool(); try { // Process in batches for better performance const batchSize = 10; for (let i = 0; i < crewIds.length; i += batchSize) { const batch = crewIds.slice(i, i + batchSize); for (const id of batch) { try { const query = ` WITH base AS ( SELECT ${requiredFields} FROM revised_base_view WHERE CREW_CODE = ? ) SELECT * FROM base ORDER BY SIGN_ON_DATE DESC LIMIT 1; `; // Note: For parameterized queries, we need to format differently // This is a simplified version - consider using proper parameterization const formattedQuery = query.replace('?', `'${id.replace(/'/g, "''")}'`); const result = await client.execute(formattedQuery); const results = result.rows.map((row) => { const obj = {}; result.columns.forEach((col, index) => { obj[col] = row[index]; }); return obj; }); const formattedResult = { crew_id: id, results: results }; allResults.push(formattedResult); } catch (error) { logger.error(`Error retrieving seafarer details for ${id}:`, error); // Continue processing other crew IDs allResults.push({ crew_id: id, results: [], error: error instanceof Error ? error.message : 'Unknown error' }); } } } } finally { await returnConnectionToPool(client); } } catch (error) { logger.error('Error in getSeafarerDetailsFromSnowflake:', error); throw error; } logger.info(`Retrieved seafarer details for ${allResults.length} crew members`); return allResults; } //# sourceMappingURL=snowflake.js.map