@rashidazarang/airtable-mcp 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/custom.md +10 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/CLAUDE_INTEGRATION.md +109 -0
- package/CONTRIBUTING.md +81 -0
- package/Dockerfile +39 -0
- package/INSTALLATION.md +183 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/RELEASE_NOTES_v1.2.0.md +50 -0
- package/airtable-mcp-1.1.0.tgz +0 -0
- package/airtable_mcp/__init__.py +5 -0
- package/airtable_mcp/src/server.py +329 -0
- package/bin/airtable-crud-cli.js +445 -0
- package/bin/airtable-mcp.js +44 -0
- package/examples/airtable-crud-example.js +203 -0
- package/examples/building-mcp.md +6666 -0
- package/examples/claude_config.json +4 -0
- package/examples/env-demo.js +172 -0
- package/examples/example-tasks-update.json +23 -0
- package/examples/example-tasks.json +26 -0
- package/examples/example_usage.md +124 -0
- package/examples/sample-transform.js +76 -0
- package/examples/windsurf_mcp_config.json +17 -0
- package/index.js +179 -0
- package/inspector.py +148 -0
- package/inspector_server.py +301 -0
- package/package.json +40 -0
- package/publish-steps.txt +27 -0
- package/rashidazarang-airtable-mcp-1.1.0.tgz +0 -0
- package/rashidazarang-airtable-mcp-1.2.0.tgz +0 -0
- package/requirements.txt +10 -0
- package/setup.py +29 -0
- package/smithery.yaml +41 -0
- package/test_client.py +63 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command-line interface for Airtable CRUD operations
|
|
5
|
+
*/
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const dotenv = require('dotenv');
|
|
9
|
+
const baseUtils = require('../tools/airtable-base');
|
|
10
|
+
const crudUtils = require('../tools/airtable-crud');
|
|
11
|
+
const schemaUtils = require('../tools/airtable-schema');
|
|
12
|
+
|
|
13
|
+
// Load environment variables
|
|
14
|
+
dotenv.config();
|
|
15
|
+
|
|
16
|
+
// Get the base ID from environment variables
|
|
17
|
+
const baseId = process.env.AIRTABLE_BASE_ID;
|
|
18
|
+
if (!baseId) {
|
|
19
|
+
console.error('Error: AIRTABLE_BASE_ID not set in .env file');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Parse command line arguments
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const command = args[0];
|
|
26
|
+
|
|
27
|
+
// Display help if no command is provided
|
|
28
|
+
if (!command) {
|
|
29
|
+
showHelp();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Process the command
|
|
34
|
+
processCommand(command, args.slice(1))
|
|
35
|
+
.then(() => {
|
|
36
|
+
console.log('Command completed successfully');
|
|
37
|
+
})
|
|
38
|
+
.catch(error => {
|
|
39
|
+
console.error(`Error: ${error.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Process the command
|
|
45
|
+
* @param {string} command - The command to process
|
|
46
|
+
* @param {Array} args - The command arguments
|
|
47
|
+
*/
|
|
48
|
+
async function processCommand(command, args) {
|
|
49
|
+
switch (command) {
|
|
50
|
+
case 'list-bases':
|
|
51
|
+
await listBases();
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'list-tables':
|
|
55
|
+
await listTables();
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
case 'list-records':
|
|
59
|
+
await listRecords(args);
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
case 'get-record':
|
|
63
|
+
await getRecord(args);
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case 'create-records':
|
|
67
|
+
await createRecords(args);
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case 'update-records':
|
|
71
|
+
await updateRecords(args);
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case 'delete-records':
|
|
75
|
+
await deleteRecords(args);
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'export-records':
|
|
79
|
+
await exportRecords(args);
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'import-records':
|
|
83
|
+
await importRecords(args);
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'help':
|
|
87
|
+
showHelp();
|
|
88
|
+
break;
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
console.error(`Unknown command: ${command}`);
|
|
92
|
+
showHelp();
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* List all accessible bases
|
|
99
|
+
*/
|
|
100
|
+
async function listBases() {
|
|
101
|
+
console.log('Listing accessible bases...');
|
|
102
|
+
const bases = await baseUtils.listAllBases();
|
|
103
|
+
|
|
104
|
+
console.log(`Found ${bases.length} accessible bases:`);
|
|
105
|
+
bases.forEach(base => {
|
|
106
|
+
console.log(`- ${base.name} (${base.id})`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* List all tables in the base
|
|
112
|
+
*/
|
|
113
|
+
async function listTables() {
|
|
114
|
+
console.log(`Listing tables in base ${baseId}...`);
|
|
115
|
+
const tables = await baseUtils.listTables(baseId);
|
|
116
|
+
|
|
117
|
+
console.log(`Found ${tables.length} tables:`);
|
|
118
|
+
tables.forEach(table => {
|
|
119
|
+
console.log(`- ${table.name} (${table.id})`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* List records from a table
|
|
125
|
+
* @param {Array} args - Command arguments
|
|
126
|
+
*/
|
|
127
|
+
async function listRecords(args) {
|
|
128
|
+
if (args.length < 1) {
|
|
129
|
+
console.error('Error: Table name is required');
|
|
130
|
+
console.log('Usage: node airtable-crud-cli.js list-records <tableName> [maxRecords] [filterFormula]');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const tableName = args[0];
|
|
135
|
+
const maxRecords = args[1] ? parseInt(args[1]) : 100;
|
|
136
|
+
const filterFormula = args[2] || null;
|
|
137
|
+
|
|
138
|
+
console.log(`Listing records from table "${tableName}"...`);
|
|
139
|
+
console.log(`Max records: ${maxRecords}`);
|
|
140
|
+
if (filterFormula) {
|
|
141
|
+
console.log(`Filter: ${filterFormula}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const records = await crudUtils.readRecords(baseId, tableName, maxRecords, filterFormula);
|
|
145
|
+
|
|
146
|
+
console.log(`Found ${records.length} records:`);
|
|
147
|
+
records.forEach(record => {
|
|
148
|
+
console.log(`- ${record.id}: ${JSON.stringify(record)}`);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get a specific record by ID
|
|
154
|
+
* @param {Array} args - Command arguments
|
|
155
|
+
*/
|
|
156
|
+
async function getRecord(args) {
|
|
157
|
+
if (args.length < 2) {
|
|
158
|
+
console.error('Error: Table name and record ID are required');
|
|
159
|
+
console.log('Usage: node airtable-crud-cli.js get-record <tableName> <recordId>');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tableName = args[0];
|
|
164
|
+
const recordId = args[1];
|
|
165
|
+
|
|
166
|
+
console.log(`Getting record ${recordId} from table "${tableName}"...`);
|
|
167
|
+
|
|
168
|
+
const record = await crudUtils.getRecord(baseId, tableName, recordId);
|
|
169
|
+
|
|
170
|
+
console.log('Record:');
|
|
171
|
+
console.log(JSON.stringify(record, null, 2));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Create records in a table
|
|
176
|
+
* @param {Array} args - Command arguments
|
|
177
|
+
*/
|
|
178
|
+
async function createRecords(args) {
|
|
179
|
+
if (args.length < 2) {
|
|
180
|
+
console.error('Error: Table name and JSON file are required');
|
|
181
|
+
console.log('Usage: node airtable-crud-cli.js create-records <tableName> <jsonFile>');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const tableName = args[0];
|
|
186
|
+
const jsonFile = args[1];
|
|
187
|
+
|
|
188
|
+
// Read the JSON file
|
|
189
|
+
let records;
|
|
190
|
+
try {
|
|
191
|
+
const jsonData = fs.readFileSync(jsonFile, 'utf8');
|
|
192
|
+
records = JSON.parse(jsonData);
|
|
193
|
+
|
|
194
|
+
if (!Array.isArray(records)) {
|
|
195
|
+
console.error('Error: JSON file must contain an array of records');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error(`Error reading JSON file: ${error.message}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(`Creating ${records.length} records in table "${tableName}"...`);
|
|
204
|
+
|
|
205
|
+
const createdRecords = await crudUtils.createRecords(baseId, tableName, records);
|
|
206
|
+
|
|
207
|
+
console.log(`Created ${createdRecords.length} records`);
|
|
208
|
+
console.log('First record:');
|
|
209
|
+
console.log(JSON.stringify(createdRecords[0], null, 2));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Update records in a table
|
|
214
|
+
* @param {Array} args - Command arguments
|
|
215
|
+
*/
|
|
216
|
+
async function updateRecords(args) {
|
|
217
|
+
if (args.length < 2) {
|
|
218
|
+
console.error('Error: Table name and JSON file are required');
|
|
219
|
+
console.log('Usage: node airtable-crud-cli.js update-records <tableName> <jsonFile>');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const tableName = args[0];
|
|
224
|
+
const jsonFile = args[1];
|
|
225
|
+
|
|
226
|
+
// Read the JSON file
|
|
227
|
+
let records;
|
|
228
|
+
try {
|
|
229
|
+
const jsonData = fs.readFileSync(jsonFile, 'utf8');
|
|
230
|
+
records = JSON.parse(jsonData);
|
|
231
|
+
|
|
232
|
+
if (!Array.isArray(records)) {
|
|
233
|
+
console.error('Error: JSON file must contain an array of records');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check if records have id and fields
|
|
238
|
+
for (const record of records) {
|
|
239
|
+
if (!record.id) {
|
|
240
|
+
console.error('Error: Each record must have an id field');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!record.fields || typeof record.fields !== 'object') {
|
|
245
|
+
console.error('Error: Each record must have a fields object');
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(`Error reading JSON file: ${error.message}`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`Updating ${records.length} records in table "${tableName}"...`);
|
|
255
|
+
|
|
256
|
+
const updatedRecords = await crudUtils.updateRecords(baseId, tableName, records);
|
|
257
|
+
|
|
258
|
+
console.log(`Updated ${updatedRecords.length} records`);
|
|
259
|
+
console.log('First record:');
|
|
260
|
+
console.log(JSON.stringify(updatedRecords[0], null, 2));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Delete records from a table
|
|
265
|
+
* @param {Array} args - Command arguments
|
|
266
|
+
*/
|
|
267
|
+
async function deleteRecords(args) {
|
|
268
|
+
if (args.length < 2) {
|
|
269
|
+
console.error('Error: Table name and record IDs are required');
|
|
270
|
+
console.log('Usage: node airtable-crud-cli.js delete-records <tableName> <recordId1,recordId2,...>');
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const tableName = args[0];
|
|
275
|
+
const recordIds = args[1].split(',');
|
|
276
|
+
|
|
277
|
+
console.log(`Deleting ${recordIds.length} records from table "${tableName}"...`);
|
|
278
|
+
|
|
279
|
+
const deletedRecords = await crudUtils.deleteRecords(baseId, tableName, recordIds);
|
|
280
|
+
|
|
281
|
+
console.log(`Deleted ${deletedRecords.length} records`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Export records from a table to a JSON file
|
|
286
|
+
* @param {Array} args - Command arguments
|
|
287
|
+
*/
|
|
288
|
+
async function exportRecords(args) {
|
|
289
|
+
if (args.length < 2) {
|
|
290
|
+
console.error('Error: Table name and output file are required');
|
|
291
|
+
console.log('Usage: node airtable-crud-cli.js export-records <tableName> <outputFile> [maxRecords] [filterFormula]');
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const tableName = args[0];
|
|
296
|
+
const outputFile = args[1];
|
|
297
|
+
const maxRecords = args[2] ? parseInt(args[2]) : 100;
|
|
298
|
+
const filterFormula = args[3] || null;
|
|
299
|
+
|
|
300
|
+
console.log(`Exporting records from table "${tableName}" to ${outputFile}...`);
|
|
301
|
+
console.log(`Max records: ${maxRecords}`);
|
|
302
|
+
if (filterFormula) {
|
|
303
|
+
console.log(`Filter: ${filterFormula}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const records = await crudUtils.readRecords(baseId, tableName, maxRecords, filterFormula);
|
|
307
|
+
|
|
308
|
+
// Write records to file
|
|
309
|
+
try {
|
|
310
|
+
fs.writeFileSync(outputFile, JSON.stringify(records, null, 2));
|
|
311
|
+
console.log(`Exported ${records.length} records to ${outputFile}`);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error(`Error writing to file: ${error.message}`);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Import records from a JSON file to a table
|
|
320
|
+
* @param {Array} args - Command arguments
|
|
321
|
+
*/
|
|
322
|
+
async function importRecords(args) {
|
|
323
|
+
if (args.length < 2) {
|
|
324
|
+
console.error('Error: Table name and input file are required');
|
|
325
|
+
console.log('Usage: node airtable-crud-cli.js import-records <tableName> <inputFile> [--update] [--clear]');
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const tableName = args[0];
|
|
330
|
+
const inputFile = args[1];
|
|
331
|
+
const update = args.includes('--update');
|
|
332
|
+
const clear = args.includes('--clear');
|
|
333
|
+
|
|
334
|
+
// Read the JSON file
|
|
335
|
+
let records;
|
|
336
|
+
try {
|
|
337
|
+
const jsonData = fs.readFileSync(inputFile, 'utf8');
|
|
338
|
+
records = JSON.parse(jsonData);
|
|
339
|
+
|
|
340
|
+
if (!Array.isArray(records)) {
|
|
341
|
+
console.error('Error: JSON file must contain an array of records');
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
console.error(`Error reading JSON file: ${error.message}`);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.log(`Importing ${records.length} records to table "${tableName}"...`);
|
|
350
|
+
|
|
351
|
+
// Clear the table if requested
|
|
352
|
+
if (clear) {
|
|
353
|
+
console.log('Clearing existing records...');
|
|
354
|
+
const existingRecords = await crudUtils.readRecords(baseId, tableName, 100000);
|
|
355
|
+
|
|
356
|
+
if (existingRecords.length > 0) {
|
|
357
|
+
const recordIds = existingRecords.map(record => record.id);
|
|
358
|
+
await crudUtils.deleteRecords(baseId, tableName, recordIds);
|
|
359
|
+
console.log(`Deleted ${existingRecords.length} existing records`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Update existing records if requested
|
|
364
|
+
if (update) {
|
|
365
|
+
console.log('Updating existing records...');
|
|
366
|
+
|
|
367
|
+
// Get existing records
|
|
368
|
+
const existingRecords = await crudUtils.readRecords(baseId, tableName, 100000);
|
|
369
|
+
const existingRecordsMap = {};
|
|
370
|
+
|
|
371
|
+
// Create a map of existing records by a key field (assuming 'Name' is the key)
|
|
372
|
+
existingRecords.forEach(record => {
|
|
373
|
+
if (record.Name) {
|
|
374
|
+
existingRecordsMap[record.Name] = record;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Separate records to update and create
|
|
379
|
+
const recordsToUpdate = [];
|
|
380
|
+
const recordsToCreate = [];
|
|
381
|
+
|
|
382
|
+
records.forEach(record => {
|
|
383
|
+
if (record.Name && existingRecordsMap[record.Name]) {
|
|
384
|
+
// Record exists, update it
|
|
385
|
+
recordsToUpdate.push({
|
|
386
|
+
id: existingRecordsMap[record.Name].id,
|
|
387
|
+
fields: record
|
|
388
|
+
});
|
|
389
|
+
} else {
|
|
390
|
+
// Record doesn't exist, create it
|
|
391
|
+
recordsToCreate.push(record);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Update existing records
|
|
396
|
+
if (recordsToUpdate.length > 0) {
|
|
397
|
+
const updatedRecords = await crudUtils.updateRecords(baseId, tableName, recordsToUpdate);
|
|
398
|
+
console.log(`Updated ${updatedRecords.length} existing records`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Create new records
|
|
402
|
+
if (recordsToCreate.length > 0) {
|
|
403
|
+
const createdRecords = await crudUtils.createRecords(baseId, tableName, recordsToCreate);
|
|
404
|
+
console.log(`Created ${createdRecords.length} new records`);
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
// Create all records
|
|
408
|
+
const createdRecords = await crudUtils.createRecords(baseId, tableName, records);
|
|
409
|
+
console.log(`Created ${createdRecords.length} records`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Show help
|
|
415
|
+
*/
|
|
416
|
+
function showHelp() {
|
|
417
|
+
console.log('Airtable CRUD CLI');
|
|
418
|
+
console.log('================');
|
|
419
|
+
console.log('');
|
|
420
|
+
console.log('Usage: node airtable-crud-cli.js <command> [options]');
|
|
421
|
+
console.log('');
|
|
422
|
+
console.log('Commands:');
|
|
423
|
+
console.log(' list-bases List all accessible bases');
|
|
424
|
+
console.log(' list-tables List all tables in the base');
|
|
425
|
+
console.log(' list-records <tableName> [max] [filter] List records from a table');
|
|
426
|
+
console.log(' get-record <tableName> <recordId> Get a specific record');
|
|
427
|
+
console.log(' create-records <tableName> <jsonFile> Create records from a JSON file');
|
|
428
|
+
console.log(' update-records <tableName> <jsonFile> Update records from a JSON file');
|
|
429
|
+
console.log(' delete-records <tableName> <id1,id2,...> Delete records from a table');
|
|
430
|
+
console.log(' export-records <tableName> <file> [max] Export records to a JSON file');
|
|
431
|
+
console.log(' import-records <tableName> <file> [flags] Import records from a JSON file');
|
|
432
|
+
console.log(' help Show this help');
|
|
433
|
+
console.log('');
|
|
434
|
+
console.log('Flags for import-records:');
|
|
435
|
+
console.log(' --update Update existing records (match by Name field)');
|
|
436
|
+
console.log(' --clear Clear all existing records before import');
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log('Examples:');
|
|
439
|
+
console.log(' node airtable-crud-cli.js list-tables');
|
|
440
|
+
console.log(' node airtable-crud-cli.js list-records "My Table" 10');
|
|
441
|
+
console.log(' node airtable-crud-cli.js get-record "My Table" rec123456');
|
|
442
|
+
console.log(' node airtable-crud-cli.js create-records "My Table" data.json');
|
|
443
|
+
console.log(' node airtable-crud-cli.js export-records "My Table" export.json 1000');
|
|
444
|
+
console.log(' node airtable-crud-cli.js import-records "My Table" import.json --update');
|
|
445
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Find the Python interpreter
|
|
7
|
+
const getPythonPath = () => {
|
|
8
|
+
try {
|
|
9
|
+
const whichPython = require('child_process').execSync('which python3.10').toString().trim();
|
|
10
|
+
return whichPython;
|
|
11
|
+
} catch (e) {
|
|
12
|
+
try {
|
|
13
|
+
const whichPython = require('child_process').execSync('which python3').toString().trim();
|
|
14
|
+
return whichPython;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return 'python';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const pythonPath = getPythonPath();
|
|
22
|
+
const serverScript = path.join(__dirname, '..', 'airtable_mcp', 'src', 'server.py');
|
|
23
|
+
|
|
24
|
+
// Get the arguments
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
|
|
27
|
+
// Construct the full command
|
|
28
|
+
const serverProcess = spawn(pythonPath, [serverScript, ...args], {
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Handle process exit
|
|
33
|
+
serverProcess.on('close', (code) => {
|
|
34
|
+
process.exit(code);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Handle signals
|
|
38
|
+
process.on('SIGINT', () => {
|
|
39
|
+
serverProcess.kill('SIGINT');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
process.on('SIGTERM', () => {
|
|
43
|
+
serverProcess.kill('SIGTERM');
|
|
44
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example script demonstrating how to use the Airtable CRUD utilities
|
|
3
|
+
*/
|
|
4
|
+
const dotenv = require('dotenv');
|
|
5
|
+
const baseUtils = require('../tools/airtable-base');
|
|
6
|
+
const crudUtils = require('../tools/airtable-crud');
|
|
7
|
+
const schemaUtils = require('../tools/airtable-schema');
|
|
8
|
+
|
|
9
|
+
// Load environment variables
|
|
10
|
+
dotenv.config();
|
|
11
|
+
|
|
12
|
+
// Configuration
|
|
13
|
+
const EXAMPLE_TABLE_NAME = 'Example Tasks';
|
|
14
|
+
const EXAMPLE_RECORDS = [
|
|
15
|
+
{
|
|
16
|
+
Name: 'Complete project documentation',
|
|
17
|
+
Description: 'Write comprehensive documentation for the project',
|
|
18
|
+
Status: 'Not Started',
|
|
19
|
+
Priority: 'High',
|
|
20
|
+
DueDate: '2023-12-31'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
Name: 'Fix login bug',
|
|
24
|
+
Description: 'Users are experiencing issues with the login process',
|
|
25
|
+
Status: 'In Progress',
|
|
26
|
+
Priority: 'Critical',
|
|
27
|
+
DueDate: '2023-11-15'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
Name: 'Add new feature',
|
|
31
|
+
Description: 'Implement the new feature requested by the client',
|
|
32
|
+
Status: 'Not Started',
|
|
33
|
+
Priority: 'Medium',
|
|
34
|
+
DueDate: '2024-01-15'
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Main function to run the example
|
|
40
|
+
*/
|
|
41
|
+
async function runExample() {
|
|
42
|
+
console.log('Starting Airtable CRUD Example...\n');
|
|
43
|
+
|
|
44
|
+
const baseId = process.env.AIRTABLE_BASE_ID;
|
|
45
|
+
if (!baseId) {
|
|
46
|
+
console.error('AIRTABLE_BASE_ID not set in .env file');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Step 1: Check if we have access to the base
|
|
52
|
+
console.log('Step 1: Checking base access...');
|
|
53
|
+
const bases = await baseUtils.listAllBases();
|
|
54
|
+
const hasAccess = bases.some(base => base.id === baseId);
|
|
55
|
+
|
|
56
|
+
if (!hasAccess) {
|
|
57
|
+
throw new Error(`No access to base with ID: ${baseId}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`✅ Access confirmed to base: ${baseId}\n`);
|
|
61
|
+
|
|
62
|
+
// Step 2: List existing tables
|
|
63
|
+
console.log('Step 2: Listing existing tables...');
|
|
64
|
+
const tables = await baseUtils.listTables(baseId);
|
|
65
|
+
console.log(`Found ${tables.length} tables in the base:`);
|
|
66
|
+
tables.forEach(table => console.log(`- ${table.name}`));
|
|
67
|
+
console.log();
|
|
68
|
+
|
|
69
|
+
// Step 3: Check if our example table exists
|
|
70
|
+
console.log('Step 3: Checking if example table exists...');
|
|
71
|
+
let tableExists = await crudUtils.tableExists(baseId, EXAMPLE_TABLE_NAME);
|
|
72
|
+
|
|
73
|
+
if (tableExists) {
|
|
74
|
+
console.log(`Table "${EXAMPLE_TABLE_NAME}" already exists\n`);
|
|
75
|
+
} else {
|
|
76
|
+
console.log(`Table "${EXAMPLE_TABLE_NAME}" does not exist, creating it...\n`);
|
|
77
|
+
|
|
78
|
+
// Step 4: Create the example table
|
|
79
|
+
console.log('Step 4: Creating example table...');
|
|
80
|
+
const tableConfig = {
|
|
81
|
+
name: EXAMPLE_TABLE_NAME,
|
|
82
|
+
description: 'Example table for demonstrating CRUD operations',
|
|
83
|
+
fields: [
|
|
84
|
+
{
|
|
85
|
+
name: 'Name',
|
|
86
|
+
type: 'singleLineText',
|
|
87
|
+
description: 'Task name'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Description',
|
|
91
|
+
type: 'multilineText',
|
|
92
|
+
description: 'Task description'
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'Status',
|
|
96
|
+
type: 'singleSelect',
|
|
97
|
+
options: {
|
|
98
|
+
choices: [
|
|
99
|
+
{ name: 'Not Started' },
|
|
100
|
+
{ name: 'In Progress' },
|
|
101
|
+
{ name: 'Completed' }
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
description: 'Current status of the task'
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'Priority',
|
|
108
|
+
type: 'singleSelect',
|
|
109
|
+
options: {
|
|
110
|
+
choices: [
|
|
111
|
+
{ name: 'Low' },
|
|
112
|
+
{ name: 'Medium' },
|
|
113
|
+
{ name: 'High' },
|
|
114
|
+
{ name: 'Critical' }
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
description: 'Task priority'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'DueDate',
|
|
121
|
+
type: 'date',
|
|
122
|
+
description: 'When the task is due',
|
|
123
|
+
options: {
|
|
124
|
+
dateFormat: {
|
|
125
|
+
name: 'local'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
await schemaUtils.createTable(baseId, tableConfig);
|
|
133
|
+
console.log(`✅ Created table: ${EXAMPLE_TABLE_NAME}\n`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Step 5: Create records
|
|
137
|
+
console.log('Step 5: Creating example records...');
|
|
138
|
+
const createdRecords = await crudUtils.createRecords(baseId, EXAMPLE_TABLE_NAME, EXAMPLE_RECORDS);
|
|
139
|
+
console.log(`✅ Created ${createdRecords.length} records\n`);
|
|
140
|
+
|
|
141
|
+
// Step 6: Read all records
|
|
142
|
+
console.log('Step 6: Reading all records...');
|
|
143
|
+
const allRecords = await crudUtils.readRecords(baseId, EXAMPLE_TABLE_NAME, 100);
|
|
144
|
+
console.log(`✅ Read ${allRecords.length} records`);
|
|
145
|
+
console.log('Sample record:');
|
|
146
|
+
console.log(JSON.stringify(allRecords[0], null, 2));
|
|
147
|
+
console.log();
|
|
148
|
+
|
|
149
|
+
// Step 7: Filter records
|
|
150
|
+
console.log('Step 7: Filtering records by status...');
|
|
151
|
+
const notStartedRecords = await crudUtils.readRecords(
|
|
152
|
+
baseId,
|
|
153
|
+
EXAMPLE_TABLE_NAME,
|
|
154
|
+
100,
|
|
155
|
+
'Status="Not Started"'
|
|
156
|
+
);
|
|
157
|
+
console.log(`✅ Found ${notStartedRecords.length} records with Status="Not Started"`);
|
|
158
|
+
notStartedRecords.forEach(record => console.log(`- ${record.Name} (Priority: ${record.Priority})`));
|
|
159
|
+
console.log();
|
|
160
|
+
|
|
161
|
+
// Step 8: Update records
|
|
162
|
+
console.log('Step 8: Updating records...');
|
|
163
|
+
const recordsToUpdate = notStartedRecords.map(record => ({
|
|
164
|
+
id: record.id,
|
|
165
|
+
fields: { Status: 'In Progress' }
|
|
166
|
+
}));
|
|
167
|
+
|
|
168
|
+
const updatedRecords = await crudUtils.updateRecords(baseId, EXAMPLE_TABLE_NAME, recordsToUpdate);
|
|
169
|
+
console.log(`✅ Updated ${updatedRecords.length} records to Status="In Progress"\n`);
|
|
170
|
+
|
|
171
|
+
// Step 9: Verify updates
|
|
172
|
+
console.log('Step 9: Verifying updates...');
|
|
173
|
+
const inProgressRecords = await crudUtils.readRecords(
|
|
174
|
+
baseId,
|
|
175
|
+
EXAMPLE_TABLE_NAME,
|
|
176
|
+
100,
|
|
177
|
+
'Status="In Progress"'
|
|
178
|
+
);
|
|
179
|
+
console.log(`✅ Found ${inProgressRecords.length} records with Status="In Progress"`);
|
|
180
|
+
inProgressRecords.forEach(record => console.log(`- ${record.Name} (Priority: ${record.Priority})`));
|
|
181
|
+
console.log();
|
|
182
|
+
|
|
183
|
+
// Step 10: Delete records (optional - commented out to preserve data)
|
|
184
|
+
console.log('Step 10: Deleting records (optional)...');
|
|
185
|
+
console.log('Skipping deletion to preserve example data.');
|
|
186
|
+
console.log('To delete records, uncomment the code below:');
|
|
187
|
+
console.log('```');
|
|
188
|
+
console.log('const recordIdsToDelete = allRecords.map(record => record.id);');
|
|
189
|
+
console.log('const deletedRecords = await crudUtils.deleteRecords(baseId, EXAMPLE_TABLE_NAME, recordIdsToDelete);');
|
|
190
|
+
console.log('console.log(`✅ Deleted ${deletedRecords.length} records`);');
|
|
191
|
+
console.log('```\n');
|
|
192
|
+
|
|
193
|
+
console.log('Example completed successfully!');
|
|
194
|
+
console.log('You can now view the data in your Airtable base.');
|
|
195
|
+
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error('Error during example:', error.message);
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Run the example
|
|
203
|
+
runExample();
|