Skip to main content
Glama
IBM
by IBM
logic.ts7.02 kB
/** * @fileoverview Defines the core logic, schemas, and types for the `execute_sql` tool. * This module provides dynamic SQL execution capabilities for the IBM i database. * Unlike YAML tools with predefined statements, this tool accepts arbitrary SQL at runtime. * @module src/mcp-server/tools/executeSql/logic * @see {@link src/mcp-server/tools/executeSql/registration.ts} for the handler and registration logic. */ import { z } from "zod"; import { JsonRpcErrorCode, McpError } from "../../../types-global/errors.js"; import { getRequestContext } from "../../../utils/index.js"; import { logger } from "../../../utils/internal/logger.js"; import { IBMiConnectionPool } from "../../services/connectionPool.js"; /** * Restricted SQL keywords that are not allowed in execute-sql tool * This provides basic protection against destructive operations */ const RESTRICTED_KEYWORDS = [ "DROP", "DELETE", "TRUNCATE", "ALTER", "CREATE", "INSERT", "UPDATE", "GRANT", "REVOKE", "COMMIT", "ROLLBACK", ] as const; /** * Maximum allowed SQL statement length to prevent abuse */ const MAX_SQL_LENGTH = 10000; /** * Zod schema defining the input parameters for the `execute_sql` tool. * CRITICAL: The descriptions are sent to the LLM and must be clear. */ export const ExecuteSqlInputSchema = z.object({ sql: z .string() .min(1, "SQL statement cannot be empty.") .max( MAX_SQL_LENGTH, `SQL statement cannot exceed ${MAX_SQL_LENGTH} characters.`, ) .describe("The SQL statement to execute against the IBM i database."), }); /** * Zod schema for the successful response of the `execute_sql` tool. */ export const ExecuteSqlResponseSchema = z.object({ data: z .array(z.record(z.unknown())) .describe("Array of result rows returned by the SQL query."), rowCount: z .number() .int() .min(0) .describe("Number of rows returned by the query."), executionTimeMs: z .number() .optional() .describe("Query execution time in milliseconds."), metadata: z .record(z.unknown()) .optional() .describe("Additional metadata about the query execution."), }); // Inferred TypeScript types export type ExecuteSqlInput = z.infer<typeof ExecuteSqlInputSchema>; export type ExecuteSqlResponse = z.infer<typeof ExecuteSqlResponseSchema>; /** * Validates SQL statement for security and safety * @param sql - SQL statement to validate * @throws {McpError} If the SQL statement contains restricted keywords or patterns * @private */ function validateSqlSecurity(sql: string): void { const context = getRequestContext(); const normalizedSql = sql.trim().toUpperCase(); // Check for restricted keywords at the beginning of statements for (const keyword of RESTRICTED_KEYWORDS) { if (normalizedSql.startsWith(keyword)) { logger.warning( { ...context, keyword, sqlPrefix: sql.substring(0, 50), }, "Restricted SQL keyword detected", ); throw new McpError( JsonRpcErrorCode.ValidationError, `SQL statement cannot start with restricted keyword: ${keyword}. Only SELECT and read-only operations are allowed.`, { keyword, sqlPrefix: sql.substring(0, 50) }, ); } } // Check for potentially dangerous patterns const dangerousPatterns = [ /;\s*(DROP|DELETE|TRUNCATE|ALTER|CREATE|INSERT|UPDATE)\s+/i, /UNION\s+SELECT.*INTO\s+/i, /EXEC\s*\(/i, /(CALL|EXECUTE)\s+/i, ]; for (const pattern of dangerousPatterns) { if (pattern.test(sql)) { logger.warning( { ...context, pattern: pattern.source, sqlPrefix: sql.substring(0, 100), }, "Potentially dangerous SQL pattern detected", ); throw new McpError( JsonRpcErrorCode.ValidationError, "SQL statement contains potentially dangerous patterns. Only safe, read-only queries are allowed.", { pattern: pattern.source, sqlPrefix: sql.substring(0, 100), }, ); } } logger.debug( { ...context, sqlLength: sql.length, sqlPrefix: sql.substring(0, 50), }, "SQL security validation passed", ); } /** * Processes the core logic for the `execute_sql` tool. * This function executes arbitrary SQL statements against the IBM i database. * Only read-only operations are allowed for security. * * @param params - The validated input parameters containing the SQL statement * @returns A promise resolving with the structured response data * @throws {McpError} If the SQL execution encounters an unrecoverable issue */ export async function executeSqlLogic( params: ExecuteSqlInput, ): Promise<ExecuteSqlResponse> { const context = getRequestContext(); logger.debug( { ...context, sqlLength: params.sql.length, sqlPrefix: params.sql.substring(0, 50), }, "Processing execute SQL logic", ); // Validate SQL security before execution validateSqlSecurity(params.sql); const startTime = Date.now(); try { // Execute the SQL query using the existing connection pool const result = await IBMiConnectionPool.executeQuery( params.sql, [], // No parameters for dynamic SQL context, ); const executionTime = Date.now() - startTime; if (!result.success) { throw new McpError( JsonRpcErrorCode.DatabaseError, "SQL query execution failed", { sql: params.sql.substring(0, 100), sqlReturnCode: (result as unknown as Record<string, unknown>).sql_rc, executionTime, }, ); } const response: ExecuteSqlResponse = { data: (result.data as Record<string, unknown>[]) || [], rowCount: result.data?.length || 0, executionTimeMs: executionTime, }; // Add metadata if available from the query result if (result.metadata) { response.metadata = result.metadata as Record<string, unknown>; } logger.debug( { ...context, rowCount: response.rowCount, executionTime: response.executionTimeMs, sqlPrefix: params.sql.substring(0, 50), }, "SQL query executed successfully", ); return response; } catch (error) { const executionTime = Date.now() - startTime; logger.error( { ...context, error: error instanceof Error ? error.message : String(error), executionTime, sqlPrefix: params.sql.substring(0, 100), }, "SQL execution failed", ); // Re-throw McpErrors as-is, wrap other errors if (error instanceof McpError) { throw error; } throw new McpError( JsonRpcErrorCode.DatabaseError, `SQL execution failed: ${error instanceof Error ? error.message : String(error)}`, { sql: params.sql.substring(0, 100), executionTime, originalError: error instanceof Error ? error.name : "Unknown", }, ); } }

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/IBM/ibmi-mcp'

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