execute-query
Execute SQL queries on Firebird databases with parameterized inputs for security and FIRST/ROWS pagination support.
Instructions
Executes a SQL query in the Firebird database. Uses FIRST/ROWS for pagination.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | SQL query to execute (Firebird uses FIRST/ROWS for pagination instead of LIMIT) | |
| params | No | Parameters for parameterized queries to prevent SQL injection |
Implementation Reference
- src/tools/database.ts:139-176 (handler)Primary handler implementation for the 'execute-query' MCP tool. Validates input SQL for security, executes the query using the database layer, logs activity, handles errors, and returns formatted results in MCP content format.tools.set("execute-query", { name: "execute-query", description: "Executes a SQL query in the Firebird database. Uses FIRST/ROWS for pagination.", inputSchema: ExecuteQueryArgsSchema, handler: async (args: z.infer<typeof ExecuteQueryArgsSchema>) => { const { sql, params = [] }: { sql: string; params?: (string | number | boolean | null)[] } = args; logger.info(`Executing query: ${sql.substring(0, 100)}${sql.length > 100 ? '...' : ''}`); try { if (typeof sql !== 'string' || !validateSql(sql)) { throw new FirebirdError( `Potentially unsafe SQL query: ${sql.substring(0, 100)}${sql.length > 100 ? '...' : ''}`, 'SECURITY_ERROR' ); } const result = await executeQuery(sql, params); logger.info(`Query executed successfully, ${result.length} rows returned`); return { content: [{ type: "text", text: formatForClaude(result) }] }; } catch (error) { const errorResponse = wrapError(error); logger.error(`Error ejecutando consulta: ${errorResponse.error} [${errorResponse.errorType || 'UNKNOWN'}]`); return { content: [{ type: "text", text: formatForClaude(errorResponse) }] }; } } });
- src/tools/database.ts:31-34 (schema)Zod schema defining the input parameters for the 'execute-query' tool: sql (required string) and optional params array.export const ExecuteQueryArgsSchema = z.object({ sql: z.string().min(1).describe("SQL query to execute (Firebird uses FIRST/ROWS for pagination instead of LIMIT)"), params: z.array(z.string().or(z.number()).or(z.boolean()).or(z.null())).optional().describe("Parameters for parameterized queries to prevent SQL injection") });
- src/server/mcp-server.ts:126-133 (registration)Registers the 'execute-query' tool (included in databaseTools from setupDatabaseTools()) to the MCP server by iterating over the tools map and calling registerTool for each.const databaseTools = setupDatabaseTools(); const metadataTools = setupMetadataTools(databaseTools); const simpleTools = setupSimpleTools(); // Register all tools using the helper function for (const [name, toolDef] of databaseTools.entries()) { registerTool(name, toolDef); }
- src/db/queries.ts:81-128 (helper)Supporting function executeQuery that handles DB connection, SQL validation, actual query execution via queryDatabase, error handling, and connection cleanup. Called by the tool handler.export const executeQuery = async (sql: string, params: any[] = [], config = DEFAULT_CONFIG): Promise<any[]> => { // Try to load config from global variable first const globalConfig = getGlobalConfig(); if (globalConfig && globalConfig.database) { logger.info(`Using global configuration for executeQuery: ${globalConfig.database}`); config = globalConfig; } let db: FirebirdDatabase | null = null; try { // Validar la consulta SQL para prevenir inyección if (!validateSql(sql)) { throw new FirebirdError( `Consulta SQL potencialmente insegura: ${sql.substring(0, 100)}${sql.length > 100 ? '...' : ''}`, 'SECURITY_ERROR' ); } db = await connectToDatabase(config); const result = await queryDatabase(db, sql, params); return result; } catch (error: any) { // Propagar el error original si ya es un FirebirdError if (error instanceof FirebirdError) { throw error; } // Categorizar el error const errorMessage = `Error ejecutando consulta: ${error.message || error}`; logger.error(errorMessage); throw new FirebirdError(errorMessage, 'QUERY_ERROR', error); } finally { // Cerrar la conexión en un bloque finally para asegurar que siempre se cierre if (db) { try { await new Promise<void>((resolve) => { db?.detach((err) => { if (err) { logger.error(`Error al cerrar la conexión: ${err.message}`); } resolve(); }); }); } catch (detachError: any) { logger.error(`Error al cerrar la conexión: ${detachError.message}`); } } } };