@bilims/mcp-sqlserver
Version:
MCP Server for Microsoft SQL Server with CRUD operations and data analysis capabilities
636 lines • 26 kB
JavaScript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { DatabaseConnection } from './database/connection.js';
import { CrudTools, SelectToolSchema, InsertToolSchema, UpdateToolSchema, DeleteToolSchema, CustomQuerySchema } from './tools/crud-tools.js';
import { SchemaTools, GetTablesSchema, GetColumnsSchema } from './tools/schema-tools.js';
import { StoredProcedureTools, ExecuteStoredProcedureSchema, GetStoredProceduresSchema, GetStoredProcedureInfoSchema } from './tools/stored-procedure-tools.js';
import { TransactionTools, BeginTransactionSchema, CommitTransactionSchema, RollbackTransactionSchema, CreateSavepointSchema } from './tools/transaction-tools.js';
import { BulkTools, BulkInsertSchema, BatchUpdateSchema, BatchDeleteSchema, ImportDataSchema } from './tools/bulk-tools.js';
import { loadDatabaseConfig } from './config/database.js';
import { zodToJsonSchema } from './utils/schema-converter.js';
export class SqlServerMcpServer {
server;
db;
crudTools;
schemaTools;
storedProcedureTools;
transactionTools;
bulkTools;
isDbConnected = false;
constructor() {
try {
process.stderr.write('Initializing MCP Server instance...\n');
this.server = new McpServer({
name: 'mcp-sqlserver',
version: '1.1.0',
description: 'MCP Server for Microsoft SQL Server with CRUD operations and schema introspection'
});
process.stderr.write('Loading database configuration...\n');
let config;
try {
config = loadDatabaseConfig();
process.stderr.write('Database configuration loaded successfully.\n');
}
catch (configError) {
process.stderr.write(`Warning: Failed to load database configuration: ${configError}\n`);
process.stderr.write('Server will start without database connection.\n');
// Create a dummy config to allow server to start
config = {
host: 'localhost',
port: 1433,
database: '',
username: '',
password: '',
encrypt: false,
trustServerCertificate: false,
pool: { min: 0, max: 10 },
connectionTimeout: 15000,
requestTimeout: 15000,
enableArithAbort: true
};
}
process.stderr.write('Creating database connection...\n');
this.db = new DatabaseConnection(config);
this.crudTools = new CrudTools(this.db);
this.schemaTools = new SchemaTools(this.db);
this.storedProcedureTools = new StoredProcedureTools(this.db);
this.transactionTools = new TransactionTools(this.db);
this.bulkTools = new BulkTools(this.db);
process.stderr.write('Setting up tools and error handling...\n');
this.setupTools();
this.setupErrorHandling();
process.stderr.write('Server initialization complete.\n');
}
catch (error) {
process.stderr.write(`Error during server construction: ${error}\n`);
if (error instanceof Error && error.stack) {
process.stderr.write(`Stack trace: ${error.stack}\n`);
}
throw error;
}
}
async wrapWithDbConnection(fn) {
await this.ensureDbConnection();
return fn();
}
setupTools() {
// SELECT tool
this.server.registerTool('sql_select', {
title: 'Execute SELECT Query',
description: 'Execute a SELECT query with optional WHERE conditions, JOINs, and ordering',
inputSchema: zodToJsonSchema(SelectToolSchema)
}, async (args) => {
try {
await this.ensureDbConnection();
const params = SelectToolSchema.parse(args);
const result = await this.crudTools.executeSelect(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_select');
}
});
// INSERT tool
this.server.registerTool('sql_insert', {
title: 'Execute INSERT Query',
description: 'Insert new records into a table',
inputSchema: zodToJsonSchema(InsertToolSchema)
}, async (args) => {
try {
const params = InsertToolSchema.parse(args);
const result = await this.crudTools.executeInsert(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_insert');
}
});
// UPDATE tool
this.server.registerTool('sql_update', {
title: 'Execute UPDATE Query',
description: 'Update existing records in a table',
inputSchema: zodToJsonSchema(UpdateToolSchema)
}, async (args) => {
try {
const params = UpdateToolSchema.parse(args);
const result = await this.crudTools.executeUpdate(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_update');
}
});
// DELETE tool
this.server.registerTool('sql_delete', {
title: 'Execute DELETE Query',
description: 'Delete records from a table',
inputSchema: zodToJsonSchema(DeleteToolSchema)
}, async (args) => {
try {
const params = DeleteToolSchema.parse(args);
const result = await this.crudTools.executeDelete(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_delete');
}
});
// Custom Query tool
this.server.registerTool('sql_query', {
title: 'Execute Custom SQL Query',
description: 'Execute a custom SQL query (SELECT statements only for security)',
inputSchema: zodToJsonSchema(CustomQuerySchema)
}, async (args) => {
try {
const params = CustomQuerySchema.parse(args);
const result = await this.crudTools.executeCustomQuery(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_query');
}
});
// Schema introspection tools
this.server.registerTool('sql_get_tables', {
title: 'Get Database Tables',
description: 'List all tables in the database or specific schema',
inputSchema: zodToJsonSchema(GetTablesSchema)
}, async (args) => {
try {
const params = GetTablesSchema.parse(args);
const result = await this.schemaTools.getTables(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_tables');
}
});
this.server.registerTool('sql_get_columns', {
title: 'Get Table Columns',
description: 'Get detailed information about columns in a specific table',
inputSchema: zodToJsonSchema(GetColumnsSchema)
}, async (args) => {
try {
const params = GetColumnsSchema.parse(args);
const result = await this.schemaTools.getColumns(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_columns');
}
});
this.server.registerTool('sql_get_table_structure', {
title: 'Get Complete Table Structure',
description: 'Get complete structure of a table including columns, indexes, and foreign keys',
inputSchema: zodToJsonSchema(GetColumnsSchema)
}, async (args) => {
try {
const params = GetColumnsSchema.parse(args);
const result = await this.schemaTools.getTableStructure(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_table_structure');
}
});
this.server.registerTool('sql_get_schemas', {
title: 'Get Database Schemas',
description: 'List all available schemas in the database',
inputSchema: {}
}, async () => {
try {
const result = await this.schemaTools.getSchemas();
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_schemas');
}
});
// Connection test tool
this.server.registerTool('sql_test_connection', {
title: 'Test Database Connection',
description: 'Test the database connection and get server information',
inputSchema: {}
}, async () => {
try {
const isConnected = await this.db.testConnection();
if (isConnected) {
const serverInfo = await this.db.getServerInfo();
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
connected: true,
serverInfo
}, null, 2)
}]
};
}
else {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
connected: false,
error: 'Connection test failed'
}, null, 2)
}]
};
}
}
catch (error) {
return this.handleError(error, 'sql_test_connection');
}
});
// Stored Procedure tools
this.server.registerTool('sql_execute_stored_procedure', {
title: 'Execute Stored Procedure',
description: 'Execute a stored procedure with parameters and handle multiple result sets',
inputSchema: zodToJsonSchema(ExecuteStoredProcedureSchema)
}, async (args) => {
try {
const params = ExecuteStoredProcedureSchema.parse(args);
const result = await this.storedProcedureTools.executeStoredProcedure(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_execute_stored_procedure');
}
});
this.server.registerTool('sql_get_stored_procedures', {
title: 'Get Stored Procedures',
description: 'List all stored procedures in the database or specific schema',
inputSchema: zodToJsonSchema(GetStoredProceduresSchema)
}, async (args) => {
try {
const params = GetStoredProceduresSchema.parse(args);
const result = await this.storedProcedureTools.getStoredProcedures(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_stored_procedures');
}
});
this.server.registerTool('sql_get_stored_procedure_info', {
title: 'Get Stored Procedure Info',
description: 'Get detailed information about a specific stored procedure including parameters',
inputSchema: zodToJsonSchema(GetStoredProcedureInfoSchema)
}, async (args) => {
try {
const params = GetStoredProcedureInfoSchema.parse(args);
const result = await this.storedProcedureTools.getStoredProcedureInfo(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_stored_procedure_info');
}
});
// Transaction Management tools
this.server.registerTool('sql_begin_transaction', {
title: 'Begin Transaction',
description: 'Start a new database transaction with optional isolation level',
inputSchema: zodToJsonSchema(BeginTransactionSchema)
}, async (args) => {
try {
const params = BeginTransactionSchema.parse(args);
const result = await this.transactionTools.beginTransaction(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_begin_transaction');
}
});
this.server.registerTool('sql_commit_transaction', {
title: 'Commit Transaction',
description: 'Commit the current database transaction',
inputSchema: zodToJsonSchema(CommitTransactionSchema)
}, async (args) => {
try {
const params = CommitTransactionSchema.parse(args);
const result = await this.transactionTools.commitTransaction(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_commit_transaction');
}
});
this.server.registerTool('sql_rollback_transaction', {
title: 'Rollback Transaction',
description: 'Rollback the current database transaction or to a savepoint',
inputSchema: zodToJsonSchema(RollbackTransactionSchema)
}, async (args) => {
try {
const params = RollbackTransactionSchema.parse(args);
const result = await this.transactionTools.rollbackTransaction(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_rollback_transaction');
}
});
this.server.registerTool('sql_create_savepoint', {
title: 'Create Savepoint',
description: 'Create a savepoint within the current transaction',
inputSchema: zodToJsonSchema(CreateSavepointSchema)
}, async (args) => {
try {
const params = CreateSavepointSchema.parse(args);
const result = await this.transactionTools.createSavepoint(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_create_savepoint');
}
});
this.server.registerTool('sql_get_transaction_status', {
title: 'Get Transaction Status',
description: 'Get current transaction status and connection options',
inputSchema: {}
}, async () => {
try {
const result = await this.transactionTools.getTransactionStatus();
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_get_transaction_status');
}
});
// Bulk Operations tools
this.server.registerTool('sql_bulk_insert', {
title: 'Bulk Insert',
description: 'Insert multiple records in batches for better performance',
inputSchema: zodToJsonSchema(BulkInsertSchema)
}, async (args) => {
try {
const params = BulkInsertSchema.parse(args);
const result = await this.bulkTools.bulkInsert(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_bulk_insert');
}
});
this.server.registerTool('sql_batch_update', {
title: 'Batch Update',
description: 'Update multiple records in batches with different conditions',
inputSchema: zodToJsonSchema(BatchUpdateSchema)
}, async (args) => {
try {
const params = BatchUpdateSchema.parse(args);
const result = await this.bulkTools.batchUpdate(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_batch_update');
}
});
this.server.registerTool('sql_batch_delete', {
title: 'Batch Delete',
description: 'Delete multiple records in batches with different conditions',
inputSchema: zodToJsonSchema(BatchDeleteSchema)
}, async (args) => {
try {
const params = BatchDeleteSchema.parse(args);
const result = await this.bulkTools.batchDelete(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_batch_delete');
}
});
this.server.registerTool('sql_import_data', {
title: 'Import Data',
description: 'Import data from CSV or JSON format into a table',
inputSchema: zodToJsonSchema(ImportDataSchema)
}, async (args) => {
try {
const params = ImportDataSchema.parse(args);
const result = await this.bulkTools.importData(params);
return {
content: [{
type: 'text',
text: JSON.stringify(result, null, 2)
}]
};
}
catch (error) {
return this.handleError(error, 'sql_import_data');
}
});
}
setupErrorHandling() {
// Handle graceful shutdown
process.on('SIGINT', async () => {
await this.cleanup();
process.exit(0);
});
process.on('SIGTERM', async () => {
await this.cleanup();
process.exit(0);
});
// Handle unhandled errors
process.on('uncaughtException', async (error) => {
// Log to stderr to avoid contaminating stdio transport
process.stderr.write(`Uncaught exception: ${error.message}\n`);
await this.cleanup();
process.exit(1);
});
process.on('unhandledRejection', async (reason, promise) => {
// Log to stderr to avoid contaminating stdio transport
process.stderr.write(`Unhandled rejection: ${reason}\n`);
await this.cleanup();
process.exit(1);
});
// Handle EPIPE errors
process.on('SIGPIPE', () => {
// Silent - don't log anything to avoid pipe issues
});
}
handleError(error, toolName) {
// Log to stderr to avoid contaminating stdio transport
process.stderr.write(`Error in ${toolName}: ${error.message || error}\n`);
let errorMessage = 'An unknown error occurred';
if (error instanceof Error) {
errorMessage = error.message;
}
else if (typeof error === 'string') {
errorMessage = error;
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: errorMessage,
tool: toolName
}, null, 2)
}],
isError: true
};
}
async start() {
try {
process.stderr.write('Creating stdio transport...\n');
const transport = new StdioServerTransport();
// Handle transport errors silently to avoid contaminating stdio
transport.onclose = () => {
process.stderr.write('Transport closed.\n');
};
transport.onerror = (error) => {
process.stderr.write(`Transport error: ${error.message || error}\n`);
};
process.stderr.write('Connecting MCP server to transport...\n');
await this.server.connect(transport);
process.stderr.write('MCP server connected successfully.\n');
// Delay database connection until after initialization
process.stderr.write('Delaying database connection until after initialization...\n');
// Don't log to stdout - it contaminates the stdio transport
}
catch (error) {
process.stderr.write(`Failed to start server: ${error}\n`);
if (error instanceof Error && error.stack) {
process.stderr.write(`Stack trace: ${error.stack}\n`);
}
await this.cleanup();
process.exit(1);
}
}
async connectToDatabase() {
if (this.isDbConnected) {
return true;
}
try {
process.stderr.write('Attempting to connect to database...\n');
await this.db.connect();
process.stderr.write('Database connection established.\n');
this.isDbConnected = true;
return true;
}
catch (error) {
process.stderr.write(`Failed to connect to database: ${error}\n`);
return false;
}
}
async ensureDbConnection() {
if (!this.isDbConnected) {
const connected = await this.connectToDatabase();
if (!connected) {
throw new Error('Database connection required but not available');
}
}
}
async cleanup() {
try {
if (this.isDbConnected && this.db) {
await this.db.disconnect();
}
// Silent cleanup - don't contaminate stdio
}
catch (error) {
process.stderr.write(`Error during cleanup: ${error}\n`);
}
}
}
//# sourceMappingURL=server.js.map