Skip to main content
Glama
dataOperations.ts11.4 kB
import { getPool } from '../db.js'; import { normalizeSqlObjectName } from '../utils.js'; import { ToolResult } from './types.js'; import { validateStoredProcedurePermission, validateQueryPermission } from '../security.js'; /** * Execute a SQL Server stored procedure with parameters and return results */ export const mcp_execute_procedure = async (args: { sp_name: string; params?: object }): Promise<ToolResult<any[]>> => { const { sp_name, params } = args; console.log('Executing mcp_execute_procedure with:', args); // Security validation for stored procedure execution const permission = validateStoredProcedurePermission(sp_name); if (!permission.allowed) { return { success: false, error: permission.message || 'Stored procedure execution not allowed' }; } try { const pool = getPool(); const request = pool.request(); if (params) { for (const [key, value] of Object.entries(params)) { request.input(key, value); } } const result = await request.execute(sp_name); return { success: true, data: result.recordset }; } catch (error: any) { console.error(`Error in mcp_execute_procedure for SP ${sp_name}: ${error.message}`); return { success: false, error: error.message }; } }; /** * Get a preview of data from a SQL Server table with optional filters */ export const mcp_preview_data = async (args: { table_name: string; filters?: object; limit?: number }): Promise<ToolResult<any[]>> => { const { table_name, filters, limit = 100 } = args; console.log('Executing mcp_preview_data with:', args); try { const pool = getPool(); const request = pool.request(); const normalizedTableName = normalizeSqlObjectName(table_name); let query = `SELECT TOP ${limit} * FROM ${normalizedTableName}`; if (filters && Object.keys(filters).length > 0) { const whereClauses = Object.entries(filters).map(([key, value], index) => { const paramName = `param${index}`; request.input(paramName, value); return `${key} = @${paramName}`; }); query += ` WHERE ${whereClauses.join(' AND ')}`; } const result = await request.query(query); return { success: true, data: result.recordset }; } catch (error: any) { console.error('Error in mcp_preview_data:', error); return { success: false, error: error.message }; } }; /** * Execute a raw SQL query */ export const mcp_execute_query = async (args: { query: string }): Promise<ToolResult<any[]>> => { const { query } = args; console.log('Executing mcp_execute_query with query:', query); // Security validation for SQL query execution const permission = validateQueryPermission(query); if (!permission.allowed) { return { success: false, error: permission.message || 'Query execution not allowed' }; } try { const pool = getPool(); const result = await pool.request().query(query); return { success: true, data: result.recordset }; } catch (error: any) { // Only log the error message, not the full stack trace for cleaner output console.error(`Error in mcp_execute_query: ${error.message}`); return { success: false, error: error.message }; } }; /** * Get statistics for a specific column in a table */ export const mcp_get_column_stats = async (args: { table_name: string, column_name: string }): Promise<ToolResult<any>> => { const { table_name, column_name } = args; console.log('Executing mcp_get_column_stats with:', args); try { const pool = getPool(); const normalizedTableName = normalizeSqlObjectName(table_name); // Note: We're using double quotes for identifiers and params for values to prevent SQL injection const query = ` SELECT '${column_name}' as column_name, COUNT(*) AS total_rows, SUM(CASE WHEN [${column_name}] IS NULL THEN 1 ELSE 0 END) AS null_count, COUNT(DISTINCT [${column_name}]) AS distinct_count, MIN([${column_name}]) AS min_value, MAX([${column_name}]) AS max_value FROM ${normalizedTableName} `; const result = await pool.request().query(query); // Get sample values const sampleQuery = ` SELECT TOP 10 [${column_name}] AS value FROM ${normalizedTableName} WHERE [${column_name}] IS NOT NULL GROUP BY [${column_name}] ORDER BY COUNT(*) DESC `; const sampleResult = await pool.request().query(sampleQuery); const stats = result.recordset[0] || {}; return { success: true, data: { ...stats, sample_values: sampleResult.recordset.map(row => row.value) } }; } catch (error: any) { console.error(`Error in mcp_get_column_stats for table ${table_name}, column ${column_name}: ${error.message}`); return { success: false, error: error.message }; } }; /** * Enhanced version of preview data */ export const mcp_preview_data_enhanced = async (args: { table_name: string; filters?: Record<string, any>; limit?: number }): Promise<ToolResult<any[]>> => { const { table_name, filters, limit = 100 } = args; console.log('Executing mcp_preview_data_enhanced with:', args); try { const pool = getPool(); const request = pool.request(); const normalizedTableName = normalizeSqlObjectName(table_name); // Get column names first to handle them properly const columnsQuery = ` SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${normalizedTableName.split('.').pop()}' AND TABLE_SCHEMA = '${normalizedTableName.split('.')[0]}' `; const columnsResult = await pool.request().query(columnsQuery); // Build the query with proper handling of column names let query = `SELECT TOP ${limit} * FROM ${normalizedTableName}`; if (filters && Object.keys(filters).length > 0) { const whereClauses = Object.entries(filters).map(([key, value], index) => { const paramName = `param${index}`; request.input(paramName, value); // Handle NULL values specially if (value === null) { return `[${key}] IS NULL`; } return `[${key}] = @${paramName}`; }); query += ` WHERE ${whereClauses.join(' AND ')}`; } // Add ORDER BY for consistent results query += ` ORDER BY (SELECT NULL)`; const result = await request.query(query); return { success: true, data: result.recordset }; } catch (error: any) { console.error(`Error in mcp_preview_data_enhanced: ${error.message}`); return { success: false, error: error.message }; } }; /** * Get sample values for a column */ export const mcp_get_sample_values = async (args: { table_name: string; column_name: string; limit?: number }): Promise<ToolResult<any[]>> => { const { table_name, column_name, limit = 10 } = args; console.log('Executing mcp_get_sample_values with:', args); try { const pool = getPool(); const normalizedTableName = normalizeSqlObjectName(table_name); const query = ` SELECT TOP ${limit} [${column_name}] AS value, COUNT(*) AS frequency FROM ${normalizedTableName} WHERE [${column_name}] IS NOT NULL GROUP BY [${column_name}] ORDER BY COUNT(*) DESC, [${column_name}] `; const result = await pool.request().query(query); return { success: true, data: result.recordset }; } catch (error: any) { console.error(`Error in mcp_get_sample_values: ${error.message}`); return { success: false, error: error.message }; } }; /** * Get enhanced statistics for a column */ export const mcp_get_column_stats_enhanced = async (args: { table_name: string; column_name: string }): Promise<ToolResult<{ total_rows: number; distinct_values: number; null_count: number; min_value: any; max_value: any; sample_values: any[]; }>> => { const { table_name, column_name } = args; console.log('Executing mcp_get_column_stats_enhanced with:', args); try { const pool = getPool(); const normalizedTableName = normalizeSqlObjectName(table_name); // Get basic stats const statsQuery = ` SELECT COUNT(*) AS total_rows, COUNT(DISTINCT [${column_name}]) AS distinct_values, SUM(CASE WHEN [${column_name}] IS NULL THEN 1 ELSE 0 END) AS null_count, MIN([${column_name}]) AS min_value, MAX([${column_name}]) AS max_value FROM ${normalizedTableName} `; // Get sample values with frequencies const sampleQuery = ` SELECT TOP 10 [${column_name}] AS value, COUNT(*) AS frequency FROM ${normalizedTableName} WHERE [${column_name}] IS NOT NULL GROUP BY [${column_name}] ORDER BY COUNT(*) DESC, [${column_name}] `; const [statsResult, sampleResult] = await Promise.all([ pool.request().query(statsQuery), pool.request().query(sampleQuery) ]); const stats = statsResult.recordset[0] || {}; return { success: true, data: { total_rows: stats.total_rows || 0, distinct_values: stats.distinct_values || 0, null_count: stats.null_count || 0, min_value: stats.min_value, max_value: stats.max_value, sample_values: sampleResult.recordset } }; } catch (error: any) { console.error(`Error in mcp_get_column_stats_enhanced: ${error.message}`); return { success: false, error: error.message }; } }; /** * Perform quick data analysis on a table */ export const mcp_quick_data_analysis = async (args: { table_name: string; limit?: number }): Promise<ToolResult<{ preview_data: any[]; column_count: number; row_count: number; columns_info: any[]; }>> => { const { table_name, limit = 100 } = args; console.log('Executing mcp_quick_data_analysis with:', args); try { const pool = getPool(); const normalizedTableName = normalizeSqlObjectName(table_name); // Get table column information const columnsQuery = ` SELECT c.name AS column_name, t.name AS data_type, c.max_length, c.precision, c.scale, c.is_nullable, c.is_identity FROM sys.columns c INNER JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE c.object_id = OBJECT_ID(@table_name) ORDER BY c.column_id; `; // Get row count const countQuery = ` SELECT COUNT(*) AS row_count FROM ${normalizedTableName} `; // Get preview data const previewQuery = ` SELECT TOP ${limit} * FROM ${normalizedTableName} ORDER BY (SELECT NULL) `; // Execute all queries in parallel const [columnsResult, countResult, previewResult] = await Promise.all([ pool.request().input('table_name', normalizedTableName).query(columnsQuery), pool.request().query(countQuery), pool.request().query(previewQuery) ]); return { success: true, data: { preview_data: previewResult.recordset, column_count: columnsResult.recordset.length, row_count: countResult.recordset[0]?.row_count || 0, columns_info: columnsResult.recordset } }; } catch (error: any) { console.error(`Error in mcp_quick_data_analysis: ${error.message}`); return { success: false, error: error.message }; } };

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hendrickcastro/MCPQL'

If you have feedback or need assistance with the MCP directory API, please join our Discord server