Airtable MCP

by rashidazarang
Verified
MIT License
122
6
  • Apple
#!/usr/bin/env node /** * Command-line interface for Airtable CRUD operations */ const fs = require('fs'); const path = require('path'); const dotenv = require('dotenv'); const baseUtils = require('../tools/airtable-base'); const crudUtils = require('../tools/airtable-crud'); const schemaUtils = require('../tools/airtable-schema'); // Load environment variables dotenv.config(); // Get the base ID from environment variables const baseId = process.env.AIRTABLE_BASE_ID; if (!baseId) { console.error('Error: AIRTABLE_BASE_ID not set in .env file'); process.exit(1); } // Parse command line arguments const args = process.argv.slice(2); const command = args[0]; // Display help if no command is provided if (!command) { showHelp(); process.exit(0); } // Process the command processCommand(command, args.slice(1)) .then(() => { console.log('Command completed successfully'); }) .catch(error => { console.error(`Error: ${error.message}`); process.exit(1); }); /** * Process the command * @param {string} command - The command to process * @param {Array} args - The command arguments */ async function processCommand(command, args) { switch (command) { case 'list-bases': await listBases(); break; case 'list-tables': await listTables(); break; case 'list-records': await listRecords(args); break; case 'get-record': await getRecord(args); break; case 'create-records': await createRecords(args); break; case 'update-records': await updateRecords(args); break; case 'delete-records': await deleteRecords(args); break; case 'export-records': await exportRecords(args); break; case 'import-records': await importRecords(args); break; case 'help': showHelp(); break; default: console.error(`Unknown command: ${command}`); showHelp(); process.exit(1); } } /** * List all accessible bases */ async function listBases() { console.log('Listing accessible bases...'); const bases = await baseUtils.listAllBases(); console.log(`Found ${bases.length} accessible bases:`); bases.forEach(base => { console.log(`- ${base.name} (${base.id})`); }); } /** * List all tables in the base */ async function listTables() { console.log(`Listing tables in base ${baseId}...`); const tables = await baseUtils.listTables(baseId); console.log(`Found ${tables.length} tables:`); tables.forEach(table => { console.log(`- ${table.name} (${table.id})`); }); } /** * List records from a table * @param {Array} args - Command arguments */ async function listRecords(args) { if (args.length < 1) { console.error('Error: Table name is required'); console.log('Usage: node airtable-crud-cli.js list-records <tableName> [maxRecords] [filterFormula]'); process.exit(1); } const tableName = args[0]; const maxRecords = args[1] ? parseInt(args[1]) : 100; const filterFormula = args[2] || null; console.log(`Listing records from table "${tableName}"...`); console.log(`Max records: ${maxRecords}`); if (filterFormula) { console.log(`Filter: ${filterFormula}`); } const records = await crudUtils.readRecords(baseId, tableName, maxRecords, filterFormula); console.log(`Found ${records.length} records:`); records.forEach(record => { console.log(`- ${record.id}: ${JSON.stringify(record)}`); }); } /** * Get a specific record by ID * @param {Array} args - Command arguments */ async function getRecord(args) { if (args.length < 2) { console.error('Error: Table name and record ID are required'); console.log('Usage: node airtable-crud-cli.js get-record <tableName> <recordId>'); process.exit(1); } const tableName = args[0]; const recordId = args[1]; console.log(`Getting record ${recordId} from table "${tableName}"...`); const record = await crudUtils.getRecord(baseId, tableName, recordId); console.log('Record:'); console.log(JSON.stringify(record, null, 2)); } /** * Create records in a table * @param {Array} args - Command arguments */ async function createRecords(args) { if (args.length < 2) { console.error('Error: Table name and JSON file are required'); console.log('Usage: node airtable-crud-cli.js create-records <tableName> <jsonFile>'); process.exit(1); } const tableName = args[0]; const jsonFile = args[1]; // Read the JSON file let records; try { const jsonData = fs.readFileSync(jsonFile, 'utf8'); records = JSON.parse(jsonData); if (!Array.isArray(records)) { console.error('Error: JSON file must contain an array of records'); process.exit(1); } } catch (error) { console.error(`Error reading JSON file: ${error.message}`); process.exit(1); } console.log(`Creating ${records.length} records in table "${tableName}"...`); const createdRecords = await crudUtils.createRecords(baseId, tableName, records); console.log(`Created ${createdRecords.length} records`); console.log('First record:'); console.log(JSON.stringify(createdRecords[0], null, 2)); } /** * Update records in a table * @param {Array} args - Command arguments */ async function updateRecords(args) { if (args.length < 2) { console.error('Error: Table name and JSON file are required'); console.log('Usage: node airtable-crud-cli.js update-records <tableName> <jsonFile>'); process.exit(1); } const tableName = args[0]; const jsonFile = args[1]; // Read the JSON file let records; try { const jsonData = fs.readFileSync(jsonFile, 'utf8'); records = JSON.parse(jsonData); if (!Array.isArray(records)) { console.error('Error: JSON file must contain an array of records'); process.exit(1); } // Check if records have id and fields for (const record of records) { if (!record.id) { console.error('Error: Each record must have an id field'); process.exit(1); } if (!record.fields || typeof record.fields !== 'object') { console.error('Error: Each record must have a fields object'); process.exit(1); } } } catch (error) { console.error(`Error reading JSON file: ${error.message}`); process.exit(1); } console.log(`Updating ${records.length} records in table "${tableName}"...`); const updatedRecords = await crudUtils.updateRecords(baseId, tableName, records); console.log(`Updated ${updatedRecords.length} records`); console.log('First record:'); console.log(JSON.stringify(updatedRecords[0], null, 2)); } /** * Delete records from a table * @param {Array} args - Command arguments */ async function deleteRecords(args) { if (args.length < 2) { console.error('Error: Table name and record IDs are required'); console.log('Usage: node airtable-crud-cli.js delete-records <tableName> <recordId1,recordId2,...>'); process.exit(1); } const tableName = args[0]; const recordIds = args[1].split(','); console.log(`Deleting ${recordIds.length} records from table "${tableName}"...`); const deletedRecords = await crudUtils.deleteRecords(baseId, tableName, recordIds); console.log(`Deleted ${deletedRecords.length} records`); } /** * Export records from a table to a JSON file * @param {Array} args - Command arguments */ async function exportRecords(args) { if (args.length < 2) { console.error('Error: Table name and output file are required'); console.log('Usage: node airtable-crud-cli.js export-records <tableName> <outputFile> [maxRecords] [filterFormula]'); process.exit(1); } const tableName = args[0]; const outputFile = args[1]; const maxRecords = args[2] ? parseInt(args[2]) : 100; const filterFormula = args[3] || null; console.log(`Exporting records from table "${tableName}" to ${outputFile}...`); console.log(`Max records: ${maxRecords}`); if (filterFormula) { console.log(`Filter: ${filterFormula}`); } const records = await crudUtils.readRecords(baseId, tableName, maxRecords, filterFormula); // Write records to file try { fs.writeFileSync(outputFile, JSON.stringify(records, null, 2)); console.log(`Exported ${records.length} records to ${outputFile}`); } catch (error) { console.error(`Error writing to file: ${error.message}`); process.exit(1); } } /** * Import records from a JSON file to a table * @param {Array} args - Command arguments */ async function importRecords(args) { if (args.length < 2) { console.error('Error: Table name and input file are required'); console.log('Usage: node airtable-crud-cli.js import-records <tableName> <inputFile> [--update] [--clear]'); process.exit(1); } const tableName = args[0]; const inputFile = args[1]; const update = args.includes('--update'); const clear = args.includes('--clear'); // Read the JSON file let records; try { const jsonData = fs.readFileSync(inputFile, 'utf8'); records = JSON.parse(jsonData); if (!Array.isArray(records)) { console.error('Error: JSON file must contain an array of records'); process.exit(1); } } catch (error) { console.error(`Error reading JSON file: ${error.message}`); process.exit(1); } console.log(`Importing ${records.length} records to table "${tableName}"...`); // Clear the table if requested if (clear) { console.log('Clearing existing records...'); const existingRecords = await crudUtils.readRecords(baseId, tableName, 100000); if (existingRecords.length > 0) { const recordIds = existingRecords.map(record => record.id); await crudUtils.deleteRecords(baseId, tableName, recordIds); console.log(`Deleted ${existingRecords.length} existing records`); } } // Update existing records if requested if (update) { console.log('Updating existing records...'); // Get existing records const existingRecords = await crudUtils.readRecords(baseId, tableName, 100000); const existingRecordsMap = {}; // Create a map of existing records by a key field (assuming 'Name' is the key) existingRecords.forEach(record => { if (record.Name) { existingRecordsMap[record.Name] = record; } }); // Separate records to update and create const recordsToUpdate = []; const recordsToCreate = []; records.forEach(record => { if (record.Name && existingRecordsMap[record.Name]) { // Record exists, update it recordsToUpdate.push({ id: existingRecordsMap[record.Name].id, fields: record }); } else { // Record doesn't exist, create it recordsToCreate.push(record); } }); // Update existing records if (recordsToUpdate.length > 0) { const updatedRecords = await crudUtils.updateRecords(baseId, tableName, recordsToUpdate); console.log(`Updated ${updatedRecords.length} existing records`); } // Create new records if (recordsToCreate.length > 0) { const createdRecords = await crudUtils.createRecords(baseId, tableName, recordsToCreate); console.log(`Created ${createdRecords.length} new records`); } } else { // Create all records const createdRecords = await crudUtils.createRecords(baseId, tableName, records); console.log(`Created ${createdRecords.length} records`); } } /** * Show help */ function showHelp() { console.log('Airtable CRUD CLI'); console.log('================'); console.log(''); console.log('Usage: node airtable-crud-cli.js <command> [options]'); console.log(''); console.log('Commands:'); console.log(' list-bases List all accessible bases'); console.log(' list-tables List all tables in the base'); console.log(' list-records <tableName> [max] [filter] List records from a table'); console.log(' get-record <tableName> <recordId> Get a specific record'); console.log(' create-records <tableName> <jsonFile> Create records from a JSON file'); console.log(' update-records <tableName> <jsonFile> Update records from a JSON file'); console.log(' delete-records <tableName> <id1,id2,...> Delete records from a table'); console.log(' export-records <tableName> <file> [max] Export records to a JSON file'); console.log(' import-records <tableName> <file> [flags] Import records from a JSON file'); console.log(' help Show this help'); console.log(''); console.log('Flags for import-records:'); console.log(' --update Update existing records (match by Name field)'); console.log(' --clear Clear all existing records before import'); console.log(''); console.log('Examples:'); console.log(' node airtable-crud-cli.js list-tables'); console.log(' node airtable-crud-cli.js list-records "My Table" 10'); console.log(' node airtable-crud-cli.js get-record "My Table" rec123456'); console.log(' node airtable-crud-cli.js create-records "My Table" data.json'); console.log(' node airtable-crud-cli.js export-records "My Table" export.json 1000'); console.log(' node airtable-crud-cli.js import-records "My Table" import.json --update'); }