@bluspace/mssql-mcp-server
Version:
Microsoft SQL Server MCP (Model Context Protocol) Server - AI-powered database interaction tool
679 lines (678 loc) • 29.9 kB
JavaScript
import { z } from "zod";
import sql from "mssql";
export function registerSchemaTools(server, db) {
// listSchemas: returns all schema names
server.tool("listSchemas", "Lists all available schemas in the database", async () => {
try {
const result = await db.request().query(`SELECT SCHEMA_NAME AS schema_name FROM INFORMATION_SCHEMA.SCHEMATA`);
const schemas = result.recordset.map((r) => r.schema_name);
return {
content: [
{
type: "text",
text: JSON.stringify({ schemas })
}
]
};
}
catch (error) {
console.error("Error listing schemas:", error);
throw new Error(`Failed to list schemas: ${error.message}`);
}
});
// readSchema: tables + columns in a schema (or all schemas if not specified)
server.tool("readSchema", "Read Schema Information", {
schema: z.string().optional().describe("Name of the schema to inspect (defaults to all schemas)")
}, async ({ schema }) => {
try {
let tablesQuery;
let columnsQuery;
if (schema) {
// Get tables in the specific schema
const tablesResult = await db.request()
.input('schema', sql.NVarChar, schema)
.query(`SELECT TABLE_SCHEMA AS schema_name, TABLE_NAME AS table_name
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = @schema`);
// Get columns for all tables in the specific schema
const columnsResult = await db.request()
.input('schema', sql.NVarChar, schema)
.query(`SELECT TABLE_SCHEMA AS schema_name, TABLE_NAME AS table_name, COLUMN_NAME AS column_name, DATA_TYPE AS data_type
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @schema`);
const result = {
schema: schema,
tables: tablesResult.recordset.map((r) => r.table_name),
columns: columnsResult.recordset.map((r) => ({
table: r.table_name,
name: r.column_name,
type: r.data_type
}))
};
return {
content: [
{
type: "text",
text: JSON.stringify(result)
}
]
};
}
else {
// Get tables from all schemas
const tablesResult = await db.request()
.query(`SELECT TABLE_SCHEMA AS schema_name, TABLE_NAME AS table_name
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
ORDER BY TABLE_SCHEMA, TABLE_NAME`);
// Get columns for all tables in all schemas
const columnsResult = await db.request()
.query(`SELECT TABLE_SCHEMA AS schema_name, TABLE_NAME AS table_name, COLUMN_NAME AS column_name, DATA_TYPE AS data_type
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA IN (SELECT DISTINCT TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE')
ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION`);
// Group by schema
const schemaMap = new Map();
tablesResult.recordset.forEach((r) => {
if (!schemaMap.has(r.schema_name)) {
schemaMap.set(r.schema_name, { tables: [], columns: [] });
}
schemaMap.get(r.schema_name).tables.push(r.table_name);
});
columnsResult.recordset.forEach((r) => {
if (schemaMap.has(r.schema_name)) {
schemaMap.get(r.schema_name).columns.push({
table: r.table_name,
name: r.column_name,
type: r.data_type
});
}
});
const result = {
schemas: Array.from(schemaMap.entries()).map(([schemaName, data]) => ({
schema: schemaName,
...data
}))
};
return {
content: [
{
type: "text",
text: JSON.stringify(result)
}
]
};
}
}
catch (error) {
console.error(`Error reading schema${schema ? ' ' + schema : 's'}:`, error);
throw new Error(`Failed to read schema${schema ? ' ' + schema : 's'}: ${error.message}`);
}
});
// listTables: List all tables with metadata
server.tool("listTables", "List all tables in the database with metadata", {
schema: z.string().optional().describe("Filter by schema name"),
pattern: z.string().optional().describe("Filter by table name pattern (supports SQL wildcards)")
}, async ({ schema, pattern }) => {
try {
const request = db.request();
let query = `
SELECT
t.TABLE_SCHEMA as schema_name,
t.TABLE_NAME as table_name,
t.TABLE_TYPE as table_type,
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME) as column_count,
ISNULL(p.rows, 0) as row_count
FROM INFORMATION_SCHEMA.TABLES t
LEFT JOIN sys.tables st ON st.name = t.TABLE_NAME
LEFT JOIN sys.schemas ss ON ss.schema_id = st.schema_id AND ss.name = t.TABLE_SCHEMA
LEFT JOIN sys.partitions p ON p.object_id = st.object_id AND p.index_id IN (0,1)
WHERE t.TABLE_TYPE = 'BASE TABLE'
`;
const conditions = [];
let paramIndex = 1;
if (schema) {
conditions.push(`t.TABLE_SCHEMA = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schema);
paramIndex++;
}
if (pattern) {
conditions.push(`t.TABLE_NAME LIKE @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, pattern.replace('*', '%'));
paramIndex++;
}
if (conditions.length > 0) {
query += ` AND ${conditions.join(' AND ')}`;
}
query += ' ORDER BY t.TABLE_SCHEMA, t.TABLE_NAME';
const result = await request.query(query);
return {
content: [{
type: "text",
text: JSON.stringify({
tables: result.recordset,
total: result.recordset.length
})
}]
};
}
catch (error) {
console.error("Error listing tables:", error);
throw new Error(`Failed to list tables: ${error.message}`);
}
});
// listViews: List all views
server.tool("listViews", "List all views in the database", {
schema: z.string().optional().describe("Filter by schema name"),
pattern: z.string().optional().describe("Filter by view name pattern (supports SQL wildcards)")
}, async ({ schema, pattern }) => {
try {
const request = db.request();
let query = `
SELECT
TABLE_SCHEMA as schema_name,
TABLE_NAME as view_name,
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_SCHEMA = v.TABLE_SCHEMA AND c.TABLE_NAME = v.TABLE_NAME) as column_count
FROM INFORMATION_SCHEMA.VIEWS v
WHERE 1=1
`;
const conditions = [];
let paramIndex = 1;
if (schema) {
conditions.push(`TABLE_SCHEMA = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schema);
paramIndex++;
}
if (pattern) {
conditions.push(`TABLE_NAME LIKE @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, pattern.replace('*', '%'));
paramIndex++;
}
if (conditions.length > 0) {
query += ` AND ${conditions.join(' AND ')}`;
}
query += ' ORDER BY TABLE_SCHEMA, TABLE_NAME';
const result = await request.query(query);
return {
content: [{
type: "text",
text: JSON.stringify({
views: result.recordset,
total: result.recordset.length
})
}]
};
}
catch (error) {
console.error("Error listing views:", error);
throw new Error(`Failed to list views: ${error.message}`);
}
});
// listFunctions: List all functions
server.tool("listFunctions", "List all user-defined functions in the database", {
schema: z.string().optional().describe("Filter by schema name"),
pattern: z.string().optional().describe("Filter by function name pattern (supports SQL wildcards)"),
type: z.enum(["SCALAR", "TABLE", "ALL"]).optional().default("ALL").describe("Function type filter")
}, async ({ schema, pattern, type = "ALL" }) => {
try {
const request = db.request();
let query = `
SELECT
ROUTINE_SCHEMA as schema_name,
ROUTINE_NAME as function_name,
ROUTINE_TYPE as routine_type,
DATA_TYPE as return_type,
CREATED as created_date,
LAST_ALTERED as modified_date
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_TYPE = 'FUNCTION'
`;
const conditions = [];
let paramIndex = 1;
if (schema) {
conditions.push(`ROUTINE_SCHEMA = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schema);
paramIndex++;
}
if (pattern) {
conditions.push(`ROUTINE_NAME LIKE @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, pattern.replace('*', '%'));
paramIndex++;
}
// Additional filter based on function type
if (type !== "ALL") {
const typeQuery = `
EXISTS (
SELECT 1 FROM sys.objects o
JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE s.name = ROUTINE_SCHEMA
AND o.name = ROUTINE_NAME
AND o.type ${type === "SCALAR" ? "IN ('FN', 'FS')" : "IN ('TF', 'IF')"}
)
`;
conditions.push(typeQuery);
}
if (conditions.length > 0) {
query += ` AND ${conditions.join(' AND ')}`;
}
query += ' ORDER BY ROUTINE_SCHEMA, ROUTINE_NAME';
const result = await request.query(query);
return {
content: [{
type: "text",
text: JSON.stringify({
functions: result.recordset,
total: result.recordset.length
})
}]
};
}
catch (error) {
console.error("Error listing functions:", error);
throw new Error(`Failed to list functions: ${error.message}`);
}
});
// listIndexes: List indexes for a table
server.tool("listIndexes", "List all indexes for a specific table", {
table: z.string().describe("Table name (can include schema.table)"),
includeColumns: z.boolean().optional().default(true).describe("Include column information")
}, async ({ table, includeColumns = true }) => {
try {
const request = db.request();
// Parse table name
const parts = table.split('.');
const tableName = parts.length === 2 ? parts[1] : table;
const schemaName = parts.length === 2 ? parts[0] : 'dbo';
request.input('schema', sql.NVarChar, schemaName);
request.input('table', sql.NVarChar, tableName);
let query = `
SELECT
s.name as schema_name,
t.name as table_name,
i.name as index_name,
i.type_desc as index_type,
i.is_primary_key,
i.is_unique,
i.is_unique_constraint,
i.fill_factor,
(SELECT SUM(p.rows) FROM sys.partitions p WHERE p.object_id = t.object_id AND p.index_id = i.index_id) as row_count
`;
if (includeColumns) {
query += `,
STUFF((
SELECT ',' + c.name + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END
FROM sys.index_columns ic
JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id
ORDER BY ic.key_ordinal
FOR XML PATH('')
), 1, 1, '') as columns
`;
}
query += `
FROM sys.indexes i
JOIN sys.tables t ON i.object_id = t.object_id
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE s.name = @schema AND t.name = @table
ORDER BY i.name
`;
const result = await request.query(query);
return {
content: [{
type: "text",
text: JSON.stringify({
table: `${schemaName}.${tableName}`,
indexes: result.recordset,
total: result.recordset.length
})
}]
};
}
catch (error) {
console.error(`Error listing indexes for ${table}:`, error);
throw new Error(`Failed to list indexes for ${table}: ${error.message}`);
}
});
// listConstraints: List constraints
server.tool("listConstraints", "List all constraints for a table or schema", {
table: z.string().optional().describe("Table name (can include schema.table)"),
schema: z.string().optional().describe("Schema name (if table not specified)"),
type: z.enum(["PRIMARY KEY", "FOREIGN KEY", "CHECK", "UNIQUE", "DEFAULT", "ALL"]).optional().default("ALL")
}, async ({ table, schema, type = "ALL" }) => {
try {
const request = db.request();
let query = `
SELECT
tc.TABLE_SCHEMA as schema_name,
tc.TABLE_NAME as table_name,
tc.CONSTRAINT_NAME as constraint_name,
tc.CONSTRAINT_TYPE as constraint_type,
CASE
WHEN tc.CONSTRAINT_TYPE = 'FOREIGN KEY' THEN
(SELECT
STUFF((
SELECT ',' + COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE CONSTRAINT_NAME = tc.CONSTRAINT_NAME
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '') + ' -> ' +
rc.UNIQUE_CONSTRAINT_SCHEMA + '.' +
(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME) + '(' +
STUFF((
SELECT ',' + kcu2.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu2
WHERE kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
ORDER BY kcu2.ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '') + ')'
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
WHERE rc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME)
WHEN tc.CONSTRAINT_TYPE IN ('PRIMARY KEY', 'UNIQUE') THEN
STUFF((
SELECT ',' + COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE CONSTRAINT_NAME = tc.CONSTRAINT_NAME
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '')
ELSE ''
END as constraint_definition
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
WHERE 1=1
`;
const conditions = [];
let paramIndex = 1;
if (table) {
const parts = table.split('.');
const tableName = parts.length === 2 ? parts[1] : table;
const schemaName = parts.length === 2 ? parts[0] : 'dbo';
conditions.push(`tc.TABLE_SCHEMA = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schemaName);
paramIndex++;
conditions.push(`tc.TABLE_NAME = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, tableName);
paramIndex++;
}
else if (schema) {
conditions.push(`tc.TABLE_SCHEMA = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schema);
paramIndex++;
}
if (type !== "ALL") {
conditions.push(`tc.CONSTRAINT_TYPE = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, type);
paramIndex++;
}
if (conditions.length > 0) {
query += ` AND ${conditions.join(' AND ')}`;
}
query += ' ORDER BY tc.TABLE_SCHEMA, tc.TABLE_NAME, tc.CONSTRAINT_TYPE, tc.CONSTRAINT_NAME';
const result = await request.query(query);
return {
content: [{
type: "text",
text: JSON.stringify({
constraints: result.recordset,
total: result.recordset.length
})
}]
};
}
catch (error) {
console.error("Error listing constraints:", error);
throw new Error(`Failed to list constraints: ${error.message}`);
}
});
// listTriggers: List triggers
server.tool("listTriggers", "List all triggers in the database", {
table: z.string().optional().describe("Table name (can include schema.table)"),
schema: z.string().optional().describe("Schema name"),
pattern: z.string().optional().describe("Trigger name pattern (supports SQL wildcards)")
}, async ({ table, schema, pattern }) => {
try {
const request = db.request();
let query = `
SELECT
s.name as schema_name,
OBJECT_NAME(t.parent_id) as table_name,
t.name as trigger_name,
t.is_disabled,
t.is_instead_of_trigger,
CASE
WHEN t.is_instead_of_trigger = 1 THEN 'INSTEAD OF'
ELSE 'AFTER'
END as trigger_timing,
STUFF((
SELECT ',' +
CASE te.type_desc
WHEN 'INSERT' THEN 'INSERT'
WHEN 'UPDATE' THEN 'UPDATE'
WHEN 'DELETE' THEN 'DELETE'
END
FROM sys.trigger_events te
WHERE te.object_id = t.object_id
FOR XML PATH('')
), 1, 1, '') as trigger_events,
t.create_date,
t.modify_date
FROM sys.triggers t
JOIN sys.objects o ON t.parent_id = o.object_id
JOIN sys.schemas s ON o.schema_id = s.schema_id
WHERE t.parent_class = 1
`;
const conditions = [];
let paramIndex = 1;
if (table) {
const parts = table.split('.');
const tableName = parts.length === 2 ? parts[1] : table;
const schemaName = parts.length === 2 ? parts[0] : schema || 'dbo';
conditions.push(`s.name = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schemaName);
paramIndex++;
conditions.push(`OBJECT_NAME(t.parent_id) = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, tableName);
paramIndex++;
}
else if (schema) {
conditions.push(`s.name = @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, schema);
paramIndex++;
}
if (pattern) {
conditions.push(`t.name LIKE @param${paramIndex}`);
request.input(`param${paramIndex}`, sql.NVarChar, pattern.replace('*', '%'));
paramIndex++;
}
if (conditions.length > 0) {
query += ` AND ${conditions.join(' AND ')}`;
}
query += ' ORDER BY s.name, OBJECT_NAME(t.parent_id), t.name';
const result = await request.query(query);
return {
content: [{
type: "text",
text: JSON.stringify({
triggers: result.recordset,
total: result.recordset.length
})
}]
};
}
catch (error) {
console.error("Error listing triggers:", error);
throw new Error(`Failed to list triggers: ${error.message}`);
}
});
// getTableSchema: Get detailed schema information for a specific table
server.tool("getTableSchema", "Get detailed schema/structure information for a specific table", {
table: z.string().describe("Table name (can include schema.table)")
}, async ({ table }) => {
try {
const request = db.request();
// Parse table name
const parts = table.split('.');
const tableName = parts.length === 2 ? parts[1] : table;
const schemaName = parts.length === 2 ? parts[0] : 'dbo';
request.input('schema', sql.NVarChar, schemaName);
request.input('table', sql.NVarChar, tableName);
// Get detailed column information
const columnsResult = await request.query(`
SELECT
c.COLUMN_NAME as column_name,
c.DATA_TYPE as data_type,
c.CHARACTER_MAXIMUM_LENGTH as max_length,
c.NUMERIC_PRECISION as precision,
c.NUMERIC_SCALE as scale,
c.IS_NULLABLE as is_nullable,
c.COLUMN_DEFAULT as default_value,
c.ORDINAL_POSITION as ordinal_position,
CASE
WHEN pk.COLUMN_NAME IS NOT NULL THEN 'YES'
ELSE 'NO'
END as is_primary_key,
CASE
WHEN fk.COLUMN_NAME IS NOT NULL THEN 'YES'
ELSE 'NO'
END as is_foreign_key,
CASE
WHEN EXISTS (
SELECT 1 FROM sys.identity_columns ic
JOIN sys.columns sc ON ic.object_id = sc.object_id AND ic.column_id = sc.column_id
JOIN sys.tables st ON sc.object_id = st.object_id
JOIN sys.schemas ss ON st.schema_id = ss.schema_id
WHERE ss.name = @schema AND st.name = @table AND sc.name = c.COLUMN_NAME
) THEN 'YES'
ELSE 'NO'
END as is_identity,
CASE
WHEN EXISTS (
SELECT 1 FROM sys.computed_columns cc
JOIN sys.columns sc ON cc.object_id = sc.object_id AND cc.column_id = sc.column_id
JOIN sys.tables st ON sc.object_id = st.object_id
JOIN sys.schemas ss ON st.schema_id = ss.schema_id
WHERE ss.name = @schema AND st.name = @table AND sc.name = c.COLUMN_NAME
) THEN 'YES'
ELSE 'NO'
END as is_computed
FROM INFORMATION_SCHEMA.COLUMNS c
LEFT JOIN (
SELECT DISTINCT
kcu.TABLE_SCHEMA,
kcu.TABLE_NAME,
kcu.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
AND kcu.TABLE_SCHEMA = tc.TABLE_SCHEMA
AND kcu.TABLE_NAME = tc.TABLE_NAME
WHERE tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
) pk ON c.TABLE_SCHEMA = pk.TABLE_SCHEMA
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.COLUMN_NAME = pk.COLUMN_NAME
LEFT JOIN (
SELECT DISTINCT
kcu.TABLE_SCHEMA,
kcu.TABLE_NAME,
kcu.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
ON kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
AND kcu.TABLE_SCHEMA = tc.TABLE_SCHEMA
AND kcu.TABLE_NAME = tc.TABLE_NAME
WHERE tc.CONSTRAINT_TYPE = 'FOREIGN KEY'
) fk ON c.TABLE_SCHEMA = fk.TABLE_SCHEMA
AND c.TABLE_NAME = fk.TABLE_NAME
AND c.COLUMN_NAME = fk.COLUMN_NAME
WHERE c.TABLE_SCHEMA = @schema AND c.TABLE_NAME = @table
ORDER BY c.ORDINAL_POSITION
`);
// Get table metadata
const tableInfoResult = await request.query(`
SELECT
s.name as schema_name,
t.name as table_name,
t.create_date,
t.modify_date,
p.rows as row_count,
CAST(ROUND(((SUM(a.total_pages) * 8) / 1024.00), 2) AS NUMERIC(36, 2)) as size_mb
FROM sys.tables t
JOIN sys.schemas s ON t.schema_id = s.schema_id
JOIN sys.indexes i ON t.object_id = i.object_id
JOIN sys.partitions p ON i.object_id = p.object_id AND i.index_id = p.index_id
JOIN sys.allocation_units a ON p.partition_id = a.container_id
WHERE s.name = @schema AND t.name = @table AND i.index_id <= 1
GROUP BY s.name, t.name, t.create_date, t.modify_date, p.rows
`);
// Get primary key information
const primaryKeyResult = await request.query(`
SELECT
tc.CONSTRAINT_NAME as constraint_name,
STUFF((
SELECT ',' + COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
WHERE kcu.CONSTRAINT_NAME = tc.CONSTRAINT_NAME
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '') as columns
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
WHERE tc.TABLE_SCHEMA = @schema
AND tc.TABLE_NAME = @table
AND tc.CONSTRAINT_TYPE = 'PRIMARY KEY'
`);
// Get foreign keys
const foreignKeysResult = await request.query(`
SELECT
fk.CONSTRAINT_NAME as constraint_name,
STUFF((
SELECT ',' + COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE CONSTRAINT_NAME = fk.CONSTRAINT_NAME
ORDER BY ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '') as columns,
rc.UNIQUE_CONSTRAINT_SCHEMA + '.' +
(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME) as referenced_table,
STUFF((
SELECT ',' + kcu2.COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu2
WHERE kcu2.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME
ORDER BY kcu2.ORDINAL_POSITION
FOR XML PATH('')
), 1, 1, '') as referenced_columns
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS fk
JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc
ON fk.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
WHERE fk.TABLE_SCHEMA = @schema
AND fk.TABLE_NAME = @table
AND fk.CONSTRAINT_TYPE = 'FOREIGN KEY'
`);
if (columnsResult.recordset.length === 0) {
throw new Error(`Table ${schemaName}.${tableName} not found`);
}
const result = {
table: {
schema: schemaName,
name: tableName,
...tableInfoResult.recordset[0]
},
columns: columnsResult.recordset,
primaryKey: primaryKeyResult.recordset[0] || null,
foreignKeys: foreignKeysResult.recordset,
columnCount: columnsResult.recordset.length
};
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
catch (error) {
console.error(`Error getting table schema for ${table}:`, error);
throw new Error(`Failed to get table schema for ${table}: ${error.message}`);
}
});
}