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
JavaScript
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);
});
}