describe-batch-tables
Retrieve detailed schema information for multiple Firebird database tables in parallel to optimize performance and streamline database analysis workflows.
Instructions
Gets the detailed schema of multiple tables in parallel for improved performance.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| maxConcurrent | No | Maximum number of concurrent operations (default: 5) | |
| tableNames | Yes | Array of table names to describe |
Implementation Reference
- src/tools/database.ts:100-103 (schema)Zod input schema for the 'describe-batch-tables' tool, defining tableNames array and optional maxConcurrent.export const DescribeBatchTablesArgsSchema = z.object({ tableNames: z.array(z.string().min(1)).min(1).max(20).describe("Array of table names to describe"), maxConcurrent: z.number().int().min(1).max(10).optional().default(5).describe("Maximum number of concurrent operations (default: 5)") });
- src/tools/database.ts:512-544 (registration)Registers the 'describe-batch-tables' MCP tool, providing name, description, schema reference, and inline handler that invokes describeBatchTables and formats results for MCP.// Add describe-batch-tables tool tools.set("describe-batch-tables", { name: "describe-batch-tables", description: "Gets the detailed schema of multiple tables in parallel for improved performance.", inputSchema: DescribeBatchTablesArgsSchema, handler: async (args: z.infer<typeof DescribeBatchTablesArgsSchema>) => { const { tableNames, maxConcurrent = 5 } = args; logger.info(`Describing batch of ${tableNames.length} tables with max concurrency ${maxConcurrent}`); try { const results = await describeBatchTables(tableNames, undefined, maxConcurrent); logger.info(`Batch description completed: ${results.filter(r => r.schema !== null).length} succeeded, ${results.filter(r => r.schema === null).length} failed`); return { content: [{ type: "text", text: formatForClaude(results) }] }; } catch (error) { const errorResponse = wrapError(error); logger.error(`Error describing batch tables: ${errorResponse.error} [${errorResponse.errorType || 'UNKNOWN'}]`); return { content: [{ type: "text", text: formatForClaude(errorResponse) }] }; } } });
- src/db/queries.ts:984-1069 (helper)Core helper function that describes multiple tables concurrently by batching calls to describeTable, with input validation, security checks, concurrency limiting, and per-table error handling.export const describeBatchTables = async ( tableNames: string[], config = DEFAULT_CONFIG, maxConcurrent: number = 5 ): Promise<Array<{tableName: string, schema: ColumnInfo[] | null, error?: string, errorType?: string}>> => { // Try to load config from global variable first const globalConfig = getGlobalConfig(); if (globalConfig && globalConfig.database) { logger.info(`Using global configuration for describeBatchTables: ${globalConfig.database}`); config = globalConfig; } // Validate input if (!Array.isArray(tableNames) || tableNames.length === 0) { throw new FirebirdError( 'Invalid tableNames array: must be a non-empty array of table names', 'VALIDATION_ERROR' ); } // Limit the number of tables to prevent abuse const MAX_TABLES = 20; if (tableNames.length > MAX_TABLES) { throw new FirebirdError( `Too many tables: maximum allowed is ${MAX_TABLES}`, 'VALIDATION_ERROR' ); } // Validate each table name tableNames.forEach((tableName, index) => { if (!tableName || typeof tableName !== 'string') { throw new FirebirdError( `Invalid table name at index ${index}`, 'VALIDATION_ERROR' ); } if (!validateSql(tableName)) { throw new FirebirdError( `Potentially unsafe table name at index ${index}: ${tableName}`, 'SECURITY_ERROR' ); } }); logger.info(`Describing batch of ${tableNames.length} tables`); // Execute queries in batches to limit concurrency const results: Array<{tableName: string, schema: ColumnInfo[] | null, error?: string, errorType?: string}> = []; // Process tables in batches of maxConcurrent for (let i = 0; i < tableNames.length; i += maxConcurrent) { const batch = tableNames.slice(i, i + maxConcurrent); // Execute batch in parallel const batchPromises = batch.map(async (tableName, batchIndex) => { const tableIndex = i + batchIndex; try { logger.debug(`Describing table ${tableIndex + 1}/${tableNames.length}: ${tableName}`); const schema = await describeTable(tableName, config); return { tableName, schema }; } catch (error: any) { logger.error(`Error describing table ${tableIndex + 1}: ${error.message || error}`); // Format error response const errorType = error instanceof FirebirdError ? error.type : 'TABLE_DESCRIBE_ERROR'; const errorMessage = error instanceof Error ? error.message : String(error); return { tableName, schema: null, error: errorMessage, errorType }; } }); // Wait for all queries in this batch to complete const batchResults = await Promise.all(batchPromises); results.push(...batchResults); } logger.info(`Batch description completed: ${results.filter(r => r.schema !== null).length} succeeded, ${results.filter(r => r.schema === null).length} failed`); return results; };
- src/db/queries.ts:355-446 (helper)Supporting helper function that describes the schema of a single table by querying Firebird system tables RDB$RELATION_FIELDS, RDB$FIELDS, etc., mapping to ColumnInfo with type mapping, nullability, defaults, PK detection.export const describeTable = async (tableName: string, config = DEFAULT_CONFIG): Promise<ColumnInfo[]> => { // Try to load config from global variable first const globalConfig = getGlobalConfig(); if (globalConfig && globalConfig.database) { logger.info(`Using global configuration for describeTable: ${globalConfig.database}`); config = globalConfig; } try { logger.info(`Obteniendo estructura de la tabla: ${tableName}`); if (!validateSql(tableName)) { throw new FirebirdError( `Nombre de tabla inválido: ${tableName}`, 'VALIDATION_ERROR' ); } // Consulta para obtener información de las columnas const sql = ` SELECT TRIM(rf.RDB$FIELD_NAME) as FIELD_NAME, CASE f.RDB$FIELD_TYPE WHEN 7 THEN 'SMALLINT' WHEN 8 THEN 'INTEGER' WHEN 10 THEN 'FLOAT' WHEN 12 THEN 'DATE' WHEN 13 THEN 'TIME' WHEN 14 THEN 'CHAR' WHEN 16 THEN 'BIGINT' WHEN 27 THEN 'DOUBLE PRECISION' WHEN 35 THEN 'TIMESTAMP' WHEN 37 THEN 'VARCHAR' WHEN 261 THEN 'BLOB' ELSE 'UNKNOWN' END as FIELD_TYPE, f.RDB$FIELD_LENGTH as FIELD_LENGTH, f.RDB$FIELD_SCALE as FIELD_SCALE, CASE rf.RDB$NULL_FLAG WHEN 1 THEN 0 ELSE 1 END as NULLABLE, rf.RDB$DEFAULT_SOURCE as DEFAULT_VALUE, CASE WHEN EXISTS ( SELECT 1 FROM RDB$RELATION_CONSTRAINTS rc JOIN RDB$INDEX_SEGMENTS isg ON rc.RDB$INDEX_NAME = isg.RDB$INDEX_NAME WHERE rc.RDB$RELATION_NAME = rf.RDB$RELATION_NAME AND rc.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' AND isg.RDB$FIELD_NAME = rf.RDB$FIELD_NAME ) THEN 1 ELSE 0 END as PRIMARY_KEY, CAST(rf.RDB$DESCRIPTION AS VARCHAR(500)) as DESCRIPTION FROM RDB$RELATION_FIELDS rf JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME WHERE rf.RDB$RELATION_NAME = ? ORDER BY rf.RDB$FIELD_POSITION `; const columns = await executeQuery(sql, [tableName], config); if (columns.length === 0) { logger.warn(`No se encontraron columnas para la tabla: ${tableName}`); throw new FirebirdError( `No se encontraron columnas para la tabla: ${tableName}. Es posible que la tabla no exista.`, 'TABLE_NOT_FOUND' ); } logger.info(`Se encontraron ${columns.length} columnas para la tabla: ${tableName}`); return columns.map((col: any) => ({ field_name: col.FIELD_NAME, field_type: col.FIELD_TYPE, field_length: col.FIELD_LENGTH, field_scale: col.FIELD_SCALE !== null ? -1 * col.FIELD_SCALE : undefined, nullable: Boolean(col.NULLABLE), default_value: col.DEFAULT_VALUE, primary_key: Boolean(col.PRIMARY_KEY), description: col.DESCRIPTION || null })); } catch (error: any) { // Propagar el error si ya es un FirebirdError if (error instanceof FirebirdError) { throw error; } const errorMessage = `Error describiendo la tabla ${tableName}: ${error.message || error}`; logger.error(errorMessage); throw new FirebirdError(errorMessage, 'TABLE_DESCRIBE_ERROR', error); } };