@bluspace/mssql-mcp-server
Version:
Microsoft SQL Server MCP (Model Context Protocol) Server - AI-powered database interaction tool
149 lines (148 loc) • 6.64 kB
JavaScript
import { z } from "zod";
import sql from "mssql";
export function registerCrudTools(server, db) {
// Helper function to safely quote a table name for SQL Server
function quotedTableName(tableName) {
// Handle schema-qualified table names (schema.table)
const parts = tableName.split('.');
if (parts.length === 2) {
return `[${parts[0]}].[${parts[1]}]`;
}
// Default to dbo schema if no schema specified
return `[dbo].[${tableName}]`;
}
// createRow: insert into table
server.tool("createRow", "Create a new row", {
table: z.string().describe("Table name to insert into"),
row: z.record(z.any()).describe("Object with column name:value pairs")
}, async ({ table, row }) => {
try {
// For SQL Server, we need to use parameterized queries differently
const request = new sql.Request(db);
const cols = Object.keys(row).map(k => `[${k}]`).join(",");
const paramNames = Object.keys(row).map(k => `@${k}`).join(",");
// Add parameters to the request
Object.entries(row).forEach(([key, value]) => {
request.input(key, value);
});
// Use OUTPUT to get the inserted row
const result = await request.query(`INSERT INTO ${quotedTableName(table)}(${cols})
OUTPUT INSERTED.*
VALUES(${paramNames})`);
return {
content: [{
type: "text",
text: JSON.stringify({ inserted: result.recordset[0] })
}]
};
}
catch (error) {
console.error(`Error creating row in ${table}:`, error);
throw new Error(`Failed to create row in ${table}: ${error.message}`);
}
});
// readRows: fetch rows by filter
server.tool("readRows", "Read rows from a table", {
table: z.string().describe("Table name to read from (can be schema.table or just table for dbo schema)"),
filter: z.record(z.any()).optional().describe("Optional filter conditions as key:value pairs"),
limit: z.number().optional().default(100).describe("Maximum number of rows to return (default: 100)")
}, async ({ table, filter = {}, limit = 100 }) => {
try {
const request = new sql.Request(db);
// Add the limit parameter
request.input('limit', sql.Int, limit);
// Build WHERE clause with parameters
let whereClause = "";
if (Object.keys(filter).length > 0) {
const clauses = Object.entries(filter).map(([key, value]) => {
request.input(key, value);
return `[${key}] = @${key}`;
});
whereClause = `WHERE ${clauses.join(" AND ")}`;
}
const result = await request.query(`SELECT TOP (@limit) * FROM ${quotedTableName(table)} ${whereClause}`);
return {
content: [{
type: "text",
text: JSON.stringify({ rows: result.recordset })
}]
};
}
catch (error) {
console.error(`Error reading rows from ${table}:`, error);
throw new Error(`Failed to read rows from ${table}: ${error.message}`);
}
});
// updateRow: update by primary key(s)
server.tool("updateRow", "Update an existing row", {
table: z.string().describe("Table name to update"),
key: z.record(z.any()).describe("Primary key as key:value pairs to identify the row"),
changes: z.record(z.any()).describe("New values as column:value pairs")
}, async ({ table, key, changes }) => {
try {
const request = new sql.Request(db);
// Build SET clause with parameters
const setClauses = Object.entries(changes).map(([k, v]) => {
const paramName = `set_${k}`;
request.input(paramName, v);
return `[${k}] = @${paramName}`;
}).join(",");
// Build WHERE clause with parameters
const whereClauses = Object.entries(key).map(([k, v]) => {
const paramName = `where_${k}`;
request.input(paramName, v);
return `[${k}] = @${paramName}`;
}).join(" AND ");
// Use OUTPUT to get the updated row
const result = await request.query(`UPDATE ${quotedTableName(table)}
SET ${setClauses}
OUTPUT INSERTED.*
WHERE ${whereClauses}`);
if (result.recordset.length === 0) {
throw new Error(`No row found matching the provided key in table ${table}`);
}
return {
content: [{
type: "text",
text: JSON.stringify({ updated: result.recordset[0] })
}]
};
}
catch (error) {
console.error(`Error updating row in ${table}:`, error);
throw new Error(`Failed to update row in ${table}: ${error.message}`);
}
});
// deleteRow: delete by primary key(s)
server.tool("deleteRow", "Delete a row", {
table: z.string().describe("Table name to delete from"),
key: z.record(z.any()).describe("Primary key as key:value pairs to identify the row")
}, async ({ table, key }) => {
try {
const request = new sql.Request(db);
// Build WHERE clause with parameters
const whereClauses = Object.entries(key).map(([k, v]) => {
const paramName = `where_${k}`;
request.input(paramName, v);
return `[${k}] = @${paramName}`;
}).join(" AND ");
// Use OUTPUT to get the deleted row
const result = await request.query(`DELETE FROM ${quotedTableName(table)}
OUTPUT DELETED.*
WHERE ${whereClauses}`);
if (result.recordset.length === 0) {
throw new Error(`No row found matching the provided key in table ${table}`);
}
return {
content: [{
type: "text",
text: JSON.stringify({ deleted: result.recordset[0] })
}]
};
}
catch (error) {
console.error(`Error deleting row from ${table}:`, error);
throw new Error(`Failed to delete row from ${table}: ${error.message}`);
}
});
}