UNPKG

mssql-mcp-server

Version:

MCP server for Microsoft SQL Server database access and comprehensive schema exploration using tedious. Includes enhanced stored procedure tools for complete SQL source code access.

214 lines (213 loc) 8.43 kB
import { Connection, Request } from "tedious"; export class ConnectionManager { connections = new Map(); defaultConnectionString; windowsCredentials = {}; namedConnections = {}; constructor() { this.initializeFromEnvironment(); } initializeFromEnvironment() { this.defaultConnectionString = process.env.MSSQL_CONNECTION_STRING; this.loadWindowsCredentials(); this.loadNamedConnections(); } loadWindowsCredentials() { try { if (process.env.WINDOWS_USERNAME || process.env.WINDOWS_PASSWORD || process.env.WINDOWS_DOMAIN) { if (process.env.WINDOWS_USERNAME) { this.windowsCredentials.username = process.env.WINDOWS_USERNAME; } if (process.env.WINDOWS_PASSWORD) { this.windowsCredentials.password = process.env.WINDOWS_PASSWORD; } if (process.env.WINDOWS_DOMAIN) { this.windowsCredentials.domain = process.env.WINDOWS_DOMAIN; } } else if (process.env.windows_credentials) { this.windowsCredentials = JSON.parse(process.env.windows_credentials); } else if (process.env.MSSQL_WINDOWS_CREDENTIALS) { this.windowsCredentials = JSON.parse(process.env.MSSQL_WINDOWS_CREDENTIALS); } else { if (process.env.MSSQL_USERNAME) { this.windowsCredentials.username = process.env.MSSQL_USERNAME; } if (process.env.MSSQL_PASSWORD) { this.windowsCredentials.password = process.env.MSSQL_PASSWORD; } if (process.env.MSSQL_DOMAIN) { this.windowsCredentials.domain = process.env.MSSQL_DOMAIN; } } } catch (error) { console.error('Error parsing Windows credentials:', error); } } loadNamedConnections() { try { const connectionVars = {}; for (const [key, value] of Object.entries(process.env)) { if (key.startsWith('CONNECTION_') && typeof value === 'string') { const connectionName = key.substring(11).toLowerCase(); connectionVars[connectionName] = value; } } if (Object.keys(connectionVars).length > 0) { this.namedConnections = connectionVars; } else if (process.env.connections) { this.namedConnections = JSON.parse(process.env.connections); } else if (process.env.MSSQL_CONNECTIONS) { this.namedConnections = JSON.parse(process.env.MSSQL_CONNECTIONS); } } catch (error) { console.error('Error parsing named connections:', error); } } parseConnectionString(connectionString, envCredentials) { const config = { server: 'localhost', options: { encrypt: true, trustServerCertificate: true, enableArithAbort: true }, authentication: { type: 'default', options: {} } }; const parts = connectionString.split(';').filter(part => part.trim()); for (const part of parts) { const [key, value] = part.split('=').map(s => s.trim()); if (!key || !value) continue; const lowerKey = key.toLowerCase(); switch (lowerKey) { case 'server': case 'data source': config.server = value; break; case 'database': case 'initial catalog': config.options.database = value; break; case 'user id': case 'uid': config.authentication.options.userName = value; break; case 'password': case 'pwd': config.authentication.options.password = value; break; case 'integrated security': if (value.toLowerCase() === 'true' || value.toLowerCase() === 'sspi') { config.authentication.type = 'ntlm'; if (envCredentials?.username) { config.authentication.options.userName = envCredentials.username; } if (envCredentials?.password) { config.authentication.options.password = envCredentials.password; } if (envCredentials?.domain) { config.authentication.options.domain = envCredentials.domain; } if (!envCredentials?.username && !envCredentials?.password) { delete config.authentication.options.userName; delete config.authentication.options.password; } } break; case 'encrypt': config.options.encrypt = value.toLowerCase() === 'true'; break; case 'trustservercertificate': config.options.trustServerCertificate = value.toLowerCase() === 'true'; break; } } return config; } getConnectionString(providedConnectionString, connectionName) { if (providedConnectionString) { return providedConnectionString; } if (connectionName) { const namedConnection = this.namedConnections[connectionName]; if (!namedConnection) { throw new Error(`Named connection '${connectionName}' not found. Available connections: ${Object.keys(this.namedConnections).join(', ')}`); } return namedConnection; } if (this.defaultConnectionString) { return this.defaultConnectionString; } throw new Error('No connection string provided and no default connection string configured'); } async getConnection(connectionString, connectionName) { const actualConnectionString = this.getConnectionString(connectionString, connectionName); if (this.connections.has(actualConnectionString)) { const connection = this.connections.get(actualConnectionString); if (connection.state.name === 'LoggedIn') { return connection; } } const config = this.parseConnectionString(actualConnectionString, this.windowsCredentials); return new Promise((resolve, reject) => { const connection = new Connection(config); connection.on('connect', (err) => { if (err) { reject(err); } else { this.connections.set(actualConnectionString, connection); resolve(connection); } }); connection.on('error', (err) => { this.connections.delete(actualConnectionString); reject(err); }); connection.connect(); }); } getNamedConnections() { return this.namedConnections; } getDefaultConnectionString() { return this.defaultConnectionString; } closeAllConnections() { for (const connection of this.connections.values()) { connection.close(); } this.connections.clear(); } } export async function executeQuery(connection, sql) { return new Promise((resolve, reject) => { const rows = []; const request = new Request(sql, (err) => { if (err) { reject(err); } else { resolve(rows); } }); request.on('row', (columns) => { const row = {}; columns.forEach((column) => { row[column.metadata.colName] = column.value; }); rows.push(row); }); connection.execSql(request); }); }