UNPKG

@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
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}`); } }); }