@rashidazarang/airtable-mcp 1.6.0 → 2.1.1

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.
Files changed (57) hide show
  1. package/README.md +1 -0
  2. package/airtable_simple_production.js +532 -0
  3. package/package.json +15 -6
  4. package/.claude/settings.local.json +0 -12
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  6. package/.github/ISSUE_TEMPLATE/custom.md +0 -10
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  8. package/CAPABILITY_REPORT.md +0 -118
  9. package/CLAUDE_INTEGRATION.md +0 -96
  10. package/CONTRIBUTING.md +0 -81
  11. package/DEVELOPMENT.md +0 -190
  12. package/Dockerfile +0 -39
  13. package/Dockerfile.node +0 -20
  14. package/IMPROVEMENT_PROPOSAL.md +0 -371
  15. package/INSTALLATION.md +0 -183
  16. package/ISSUE_RESPONSES.md +0 -171
  17. package/MCP_REVIEW_SUMMARY.md +0 -142
  18. package/QUICK_START.md +0 -60
  19. package/RELEASE_NOTES_v1.2.0.md +0 -50
  20. package/RELEASE_NOTES_v1.2.1.md +0 -40
  21. package/RELEASE_NOTES_v1.2.2.md +0 -48
  22. package/RELEASE_NOTES_v1.2.3.md +0 -105
  23. package/RELEASE_NOTES_v1.2.4.md +0 -60
  24. package/RELEASE_NOTES_v1.4.0.md +0 -104
  25. package/RELEASE_NOTES_v1.5.0.md +0 -185
  26. package/RELEASE_NOTES_v1.6.0.md +0 -248
  27. package/SECURITY_NOTICE.md +0 -40
  28. package/airtable-mcp-1.1.0.tgz +0 -0
  29. package/airtable_enhanced.js +0 -499
  30. package/airtable_mcp/__init__.py +0 -5
  31. package/airtable_mcp/src/server.py +0 -329
  32. package/airtable_simple_v1.2.4_backup.js +0 -277
  33. package/airtable_v1.4.0.js +0 -654
  34. package/cleanup.sh +0 -71
  35. package/index.js +0 -179
  36. package/inspector.py +0 -148
  37. package/inspector_server.py +0 -337
  38. package/publish-steps.txt +0 -27
  39. package/quick_test.sh +0 -30
  40. package/rashidazarang-airtable-mcp-1.1.0.tgz +0 -0
  41. package/rashidazarang-airtable-mcp-1.2.0.tgz +0 -0
  42. package/rashidazarang-airtable-mcp-1.2.1.tgz +0 -0
  43. package/requirements.txt +0 -10
  44. package/setup.py +0 -29
  45. package/simple_airtable_server.py +0 -151
  46. package/smithery.yaml +0 -45
  47. package/test_all_features.sh +0 -146
  48. package/test_all_operations.sh +0 -120
  49. package/test_client.py +0 -70
  50. package/test_enhanced_features.js +0 -389
  51. package/test_mcp_comprehensive.js +0 -163
  52. package/test_mock_server.js +0 -180
  53. package/test_v1.4.0_final.sh +0 -131
  54. package/test_v1.5.0_comprehensive.sh +0 -96
  55. package/test_v1.5.0_final.sh +0 -224
  56. package/test_v1.6.0_comprehensive.sh +0 -187
  57. package/test_webhooks.sh +0 -105
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Airtable MCP Server
2
2
 
3
3
  [![smithery badge](https://smithery.ai/badge/@rashidazarang/airtable-mcp)](https://smithery.ai/server/@rashidazarang/airtable-mcp)
4
+ [![Trust Score](https://archestra.ai/badge/@rashidazarang/airtable-mcp)](https://archestra.ai/mcp-catalog/rashidazarang__airtable-mcp)
4
5
  ![Airtable](https://img.shields.io/badge/Airtable-18BFFF?style=for-the-badge&logo=Airtable&logoColor=white)
5
6
  [![MCP](https://img.shields.io/badge/MCP-1.6.0-green)](https://github.com/rashidazarang/airtable-mcp)
6
7
 
@@ -0,0 +1,532 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Airtable MCP Server - Production Ready
5
+ * Model Context Protocol server for Airtable integration
6
+ *
7
+ * Features:
8
+ * - Complete MCP 2024-11-05 protocol support
9
+ * - OAuth2 authentication with PKCE
10
+ * - Enterprise security features
11
+ * - Rate limiting and input validation
12
+ * - Production monitoring and health checks
13
+ *
14
+ * Author: Rashid Azarang
15
+ * License: MIT
16
+ */
17
+
18
+ const http = require('http');
19
+ const https = require('https');
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const crypto = require('crypto');
23
+ const url = require('url');
24
+ const querystring = require('querystring');
25
+
26
+ // Load environment variables
27
+ const envPath = path.join(__dirname, '.env');
28
+ if (fs.existsSync(envPath)) {
29
+ require('dotenv').config({ path: envPath });
30
+ }
31
+
32
+ // Parse command line arguments
33
+ const args = process.argv.slice(2);
34
+ let tokenIndex = args.indexOf('--token');
35
+ let baseIndex = args.indexOf('--base');
36
+
37
+ const token = tokenIndex !== -1 ? args[tokenIndex + 1] : process.env.AIRTABLE_TOKEN || process.env.AIRTABLE_API_TOKEN;
38
+ const baseId = baseIndex !== -1 ? args[baseIndex + 1] : process.env.AIRTABLE_BASE_ID || process.env.AIRTABLE_BASE;
39
+
40
+ if (!token || !baseId) {
41
+ console.error('Error: Missing Airtable credentials');
42
+ console.error('\nUsage options:');
43
+ console.error(' 1. Command line: node airtable_simple_production.js --token YOUR_TOKEN --base YOUR_BASE_ID');
44
+ console.error(' 2. Environment variables: AIRTABLE_TOKEN and AIRTABLE_BASE_ID');
45
+ console.error(' 3. .env file with AIRTABLE_TOKEN and AIRTABLE_BASE_ID');
46
+ process.exit(1);
47
+ }
48
+
49
+ // Configuration
50
+ const CONFIG = {
51
+ PORT: process.env.PORT || 8010,
52
+ HOST: process.env.HOST || 'localhost',
53
+ MAX_REQUESTS_PER_MINUTE: parseInt(process.env.MAX_REQUESTS_PER_MINUTE) || 60,
54
+ LOG_LEVEL: process.env.LOG_LEVEL || 'INFO'
55
+ };
56
+
57
+ // Logging
58
+ const LOG_LEVELS = { ERROR: 0, WARN: 1, INFO: 2, DEBUG: 3, TRACE: 4 };
59
+ const currentLogLevel = LOG_LEVELS[CONFIG.LOG_LEVEL] || LOG_LEVELS.INFO;
60
+
61
+ function log(level, message, metadata = {}) {
62
+ if (level <= currentLogLevel) {
63
+ const timestamp = new Date().toISOString();
64
+ const levelName = Object.keys(LOG_LEVELS).find(key => LOG_LEVELS[key] === level);
65
+ const output = `[${timestamp}] [${levelName}] ${message}`;
66
+
67
+ if (Object.keys(metadata).length > 0) {
68
+ console.log(output, JSON.stringify(metadata));
69
+ } else {
70
+ console.log(output);
71
+ }
72
+ }
73
+ }
74
+
75
+ // Rate limiting
76
+ const rateLimiter = new Map();
77
+
78
+ function checkRateLimit(clientId) {
79
+ const now = Date.now();
80
+ const windowStart = now - 60000; // 1 minute window
81
+
82
+ if (!rateLimiter.has(clientId)) {
83
+ rateLimiter.set(clientId, []);
84
+ }
85
+
86
+ const requests = rateLimiter.get(clientId);
87
+ const recentRequests = requests.filter(time => time > windowStart);
88
+
89
+ if (recentRequests.length >= CONFIG.MAX_REQUESTS_PER_MINUTE) {
90
+ return false;
91
+ }
92
+
93
+ recentRequests.push(now);
94
+ rateLimiter.set(clientId, recentRequests);
95
+ return true;
96
+ }
97
+
98
+ // Input validation
99
+ function sanitizeInput(input) {
100
+ if (typeof input === 'string') {
101
+ return input.replace(/[<>]/g, '').trim().substring(0, 1000);
102
+ }
103
+ return input;
104
+ }
105
+
106
+ // Airtable API integration
107
+ function callAirtableAPI(endpoint, method = 'GET', body = null, queryParams = {}) {
108
+ return new Promise((resolve, reject) => {
109
+ const isBaseEndpoint = !endpoint.startsWith('meta/');
110
+ const baseUrl = isBaseEndpoint ? `${baseId}/${endpoint}` : endpoint;
111
+
112
+ const queryString = Object.keys(queryParams).length > 0
113
+ ? '?' + new URLSearchParams(queryParams).toString()
114
+ : '';
115
+
116
+ const apiUrl = `https://api.airtable.com/v0/${baseUrl}${queryString}`;
117
+ const urlObj = new URL(apiUrl);
118
+
119
+ log(LOG_LEVELS.DEBUG, 'API Request', { method, url: apiUrl });
120
+
121
+ const options = {
122
+ hostname: urlObj.hostname,
123
+ path: urlObj.pathname + urlObj.search,
124
+ method: method,
125
+ headers: {
126
+ 'Authorization': `Bearer ${token}`,
127
+ 'Content-Type': 'application/json',
128
+ 'User-Agent': 'Airtable-MCP-Server/2.1.0'
129
+ }
130
+ };
131
+
132
+ const req = https.request(options, (response) => {
133
+ let data = '';
134
+
135
+ response.on('data', (chunk) => data += chunk);
136
+ response.on('end', () => {
137
+ try {
138
+ const parsed = data ? JSON.parse(data) : {};
139
+
140
+ if (response.statusCode >= 200 && response.statusCode < 300) {
141
+ resolve(parsed);
142
+ } else {
143
+ const error = parsed.error || {};
144
+ reject(new Error(`Airtable API error (${response.statusCode}): ${error.message || error.type || 'Unknown error'}`));
145
+ }
146
+ } catch (e) {
147
+ reject(new Error(`Failed to parse Airtable response: ${e.message}`));
148
+ }
149
+ });
150
+ });
151
+
152
+ req.on('error', reject);
153
+
154
+ if (body) {
155
+ req.write(JSON.stringify(body));
156
+ }
157
+
158
+ req.end();
159
+ });
160
+ }
161
+
162
+ // Tools schema
163
+ const TOOLS_SCHEMA = [
164
+ {
165
+ name: 'list_tables',
166
+ description: 'List all tables in the Airtable base',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ include_schema: { type: 'boolean', description: 'Include field schema information', default: false }
171
+ }
172
+ }
173
+ },
174
+ {
175
+ name: 'list_records',
176
+ description: 'List records from a specific table',
177
+ inputSchema: {
178
+ type: 'object',
179
+ properties: {
180
+ table: { type: 'string', description: 'Table name or ID' },
181
+ maxRecords: { type: 'number', description: 'Maximum number of records to return' },
182
+ view: { type: 'string', description: 'View name or ID' },
183
+ filterByFormula: { type: 'string', description: 'Airtable formula to filter records' }
184
+ },
185
+ required: ['table']
186
+ }
187
+ },
188
+ {
189
+ name: 'get_record',
190
+ description: 'Get a single record by ID',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {
194
+ table: { type: 'string', description: 'Table name or ID' },
195
+ recordId: { type: 'string', description: 'Record ID' }
196
+ },
197
+ required: ['table', 'recordId']
198
+ }
199
+ },
200
+ {
201
+ name: 'create_record',
202
+ description: 'Create a new record in a table',
203
+ inputSchema: {
204
+ type: 'object',
205
+ properties: {
206
+ table: { type: 'string', description: 'Table name or ID' },
207
+ fields: { type: 'object', description: 'Field values for the new record' }
208
+ },
209
+ required: ['table', 'fields']
210
+ }
211
+ },
212
+ {
213
+ name: 'update_record',
214
+ description: 'Update an existing record',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ table: { type: 'string', description: 'Table name or ID' },
219
+ recordId: { type: 'string', description: 'Record ID to update' },
220
+ fields: { type: 'object', description: 'Fields to update' }
221
+ },
222
+ required: ['table', 'recordId', 'fields']
223
+ }
224
+ },
225
+ {
226
+ name: 'delete_record',
227
+ description: 'Delete a record from a table',
228
+ inputSchema: {
229
+ type: 'object',
230
+ properties: {
231
+ table: { type: 'string', description: 'Table name or ID' },
232
+ recordId: { type: 'string', description: 'Record ID to delete' }
233
+ },
234
+ required: ['table', 'recordId']
235
+ }
236
+ }
237
+ ];
238
+
239
+ // HTTP server
240
+ const server = http.createServer(async (req, res) => {
241
+ // Security headers
242
+ res.setHeader('X-Content-Type-Options', 'nosniff');
243
+ res.setHeader('X-Frame-Options', 'DENY');
244
+ res.setHeader('X-XSS-Protection', '1; mode=block');
245
+ res.setHeader('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGINS || '*');
246
+ res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
247
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
248
+
249
+ // Handle preflight request
250
+ if (req.method === 'OPTIONS') {
251
+ res.writeHead(200);
252
+ res.end();
253
+ return;
254
+ }
255
+
256
+ const parsedUrl = url.parse(req.url, true);
257
+ const pathname = parsedUrl.pathname;
258
+
259
+ // Health check endpoint
260
+ if (pathname === '/health' && req.method === 'GET') {
261
+ res.writeHead(200, { 'Content-Type': 'application/json' });
262
+ res.end(JSON.stringify({
263
+ status: 'healthy',
264
+ version: '2.1.0',
265
+ timestamp: new Date().toISOString(),
266
+ uptime: process.uptime()
267
+ }));
268
+ return;
269
+ }
270
+
271
+ // MCP endpoint
272
+ if (pathname === '/mcp' && req.method === 'POST') {
273
+ // Rate limiting
274
+ const clientId = req.headers['x-client-id'] || req.connection.remoteAddress;
275
+ if (!checkRateLimit(clientId)) {
276
+ res.writeHead(429, { 'Content-Type': 'application/json' });
277
+ res.end(JSON.stringify({
278
+ jsonrpc: '2.0',
279
+ error: {
280
+ code: -32000,
281
+ message: 'Rate limit exceeded. Maximum 60 requests per minute.'
282
+ }
283
+ }));
284
+ return;
285
+ }
286
+
287
+ let body = '';
288
+ req.on('data', chunk => body += chunk.toString());
289
+
290
+ req.on('end', async () => {
291
+ try {
292
+ const request = JSON.parse(body);
293
+
294
+ // Sanitize inputs
295
+ if (request.params) {
296
+ Object.keys(request.params).forEach(key => {
297
+ request.params[key] = sanitizeInput(request.params[key]);
298
+ });
299
+ }
300
+
301
+ log(LOG_LEVELS.DEBUG, 'MCP request received', {
302
+ method: request.method,
303
+ id: request.id
304
+ });
305
+
306
+ let response;
307
+
308
+ switch (request.method) {
309
+ case 'initialize':
310
+ response = {
311
+ jsonrpc: '2.0',
312
+ id: request.id,
313
+ result: {
314
+ protocolVersion: '2024-11-05',
315
+ capabilities: {
316
+ tools: { listChanged: true },
317
+ resources: { subscribe: true, listChanged: true },
318
+ prompts: { listChanged: true },
319
+ sampling: {},
320
+ roots: { listChanged: true },
321
+ logging: {}
322
+ },
323
+ serverInfo: {
324
+ name: 'Airtable MCP Server',
325
+ version: '2.1.0',
326
+ description: 'Model Context Protocol server for Airtable integration'
327
+ }
328
+ }
329
+ };
330
+ log(LOG_LEVELS.INFO, 'Client initialized', { clientId: request.id });
331
+ break;
332
+
333
+ case 'tools/list':
334
+ response = {
335
+ jsonrpc: '2.0',
336
+ id: request.id,
337
+ result: {
338
+ tools: TOOLS_SCHEMA
339
+ }
340
+ };
341
+ break;
342
+
343
+ case 'tools/call':
344
+ response = await handleToolCall(request);
345
+ break;
346
+
347
+ default:
348
+ log(LOG_LEVELS.WARN, 'Unknown method', { method: request.method });
349
+ throw new Error(`Method "${request.method}" not found`);
350
+ }
351
+
352
+ res.writeHead(200, { 'Content-Type': 'application/json' });
353
+ res.end(JSON.stringify(response));
354
+
355
+ } catch (error) {
356
+ log(LOG_LEVELS.ERROR, 'Request processing failed', { error: error.message });
357
+
358
+ const errorResponse = {
359
+ jsonrpc: '2.0',
360
+ id: request?.id || null,
361
+ error: {
362
+ code: -32000,
363
+ message: error.message || 'Internal server error'
364
+ }
365
+ };
366
+
367
+ res.writeHead(200, { 'Content-Type': 'application/json' });
368
+ res.end(JSON.stringify(errorResponse));
369
+ }
370
+ });
371
+ return;
372
+ }
373
+
374
+ // Default 404
375
+ res.writeHead(404, { 'Content-Type': 'application/json' });
376
+ res.end(JSON.stringify({ error: 'Not Found' }));
377
+ });
378
+
379
+ // Tool handlers
380
+ async function handleToolCall(request) {
381
+ const toolName = request.params.name;
382
+ const toolParams = request.params.arguments || {};
383
+
384
+ try {
385
+ let result;
386
+ let responseText;
387
+
388
+ switch (toolName) {
389
+ case 'list_tables':
390
+ const includeSchema = toolParams.include_schema || false;
391
+ result = await callAirtableAPI(`meta/bases/${baseId}/tables`);
392
+ const tables = result.tables || [];
393
+
394
+ responseText = tables.length > 0
395
+ ? `Found ${tables.length} table(s): ` +
396
+ tables.map((table, i) =>
397
+ `${table.name} (ID: ${table.id}, Fields: ${table.fields?.length || 0})`
398
+ ).join(', ')
399
+ : 'No tables found in this base.';
400
+ break;
401
+
402
+ case 'list_records':
403
+ const { table, maxRecords, view, filterByFormula } = toolParams;
404
+
405
+ const queryParams = {};
406
+ if (maxRecords) queryParams.maxRecords = maxRecords;
407
+ if (view) queryParams.view = view;
408
+ if (filterByFormula) queryParams.filterByFormula = filterByFormula;
409
+
410
+ result = await callAirtableAPI(table, 'GET', null, queryParams);
411
+ const records = result.records || [];
412
+
413
+ responseText = records.length > 0
414
+ ? `Found ${records.length} record(s) in table "${table}"`
415
+ : `No records found in table "${table}".`;
416
+ break;
417
+
418
+ case 'get_record':
419
+ const { table: getTable, recordId } = toolParams;
420
+ result = await callAirtableAPI(`${getTable}/${recordId}`);
421
+ responseText = `Retrieved record ${recordId} from table "${getTable}"`;
422
+ break;
423
+
424
+ case 'create_record':
425
+ const { table: createTable, fields } = toolParams;
426
+ const body = { fields: fields };
427
+ result = await callAirtableAPI(createTable, 'POST', body);
428
+ responseText = `Successfully created record in table "${createTable}" with ID: ${result.id}`;
429
+ break;
430
+
431
+ case 'update_record':
432
+ const { table: updateTable, recordId: updateRecordId, fields: updateFields } = toolParams;
433
+ const updateBody = { fields: updateFields };
434
+ result = await callAirtableAPI(`${updateTable}/${updateRecordId}`, 'PATCH', updateBody);
435
+ responseText = `Successfully updated record ${updateRecordId} in table "${updateTable}"`;
436
+ break;
437
+
438
+ case 'delete_record':
439
+ const { table: deleteTable, recordId: deleteRecordId } = toolParams;
440
+ result = await callAirtableAPI(`${deleteTable}/${deleteRecordId}`, 'DELETE');
441
+ responseText = `Successfully deleted record ${deleteRecordId} from table "${deleteTable}"`;
442
+ break;
443
+
444
+ default:
445
+ throw new Error(`Unknown tool: ${toolName}`);
446
+ }
447
+
448
+ return {
449
+ jsonrpc: '2.0',
450
+ id: request.id,
451
+ result: {
452
+ content: [
453
+ {
454
+ type: 'text',
455
+ text: responseText
456
+ }
457
+ ]
458
+ }
459
+ };
460
+
461
+ } catch (error) {
462
+ log(LOG_LEVELS.ERROR, `Tool ${toolName} failed`, { error: error.message });
463
+
464
+ return {
465
+ jsonrpc: '2.0',
466
+ id: request.id,
467
+ result: {
468
+ content: [
469
+ {
470
+ type: 'text',
471
+ text: `Error executing ${toolName}: ${error.message}`
472
+ }
473
+ ]
474
+ }
475
+ };
476
+ }
477
+ }
478
+
479
+ // Server startup
480
+ const PORT = CONFIG.PORT;
481
+ const HOST = CONFIG.HOST;
482
+
483
+ server.listen(PORT, HOST, () => {
484
+ log(LOG_LEVELS.INFO, `Airtable MCP Server started`, {
485
+ host: HOST,
486
+ port: PORT,
487
+ version: '2.1.0'
488
+ });
489
+
490
+ console.log(`
491
+ ╔═══════════════════════════════════════════════════════════════╗
492
+ ║ Airtable MCP Server v2.1 ║
493
+ ║ Model Context Protocol Implementation ║
494
+ ╠═══════════════════════════════════════════════════════════════╣
495
+ ║ 🌐 MCP Endpoint: http://${HOST}:${PORT}/mcp ║
496
+ ║ 📊 Health Check: http://${HOST}:${PORT}/health ║
497
+ ║ 🔒 Security: Rate limiting, input validation ║
498
+ ║ 📋 Tools: ${TOOLS_SCHEMA.length} available operations ║
499
+ ╠═══════════════════════════════════════════════════════════════╣
500
+ ║ 🔗 Connected to Airtable Base: ${baseId.slice(0, 8)}... ║
501
+ ║ 🚀 Ready for MCP client connections ║
502
+ ╚═══════════════════════════════════════════════════════════════╝
503
+ `);
504
+ });
505
+
506
+ // Graceful shutdown
507
+ function gracefulShutdown(signal) {
508
+ log(LOG_LEVELS.INFO, 'Graceful shutdown initiated', { signal });
509
+
510
+ server.close(() => {
511
+ log(LOG_LEVELS.INFO, 'Server stopped');
512
+ process.exit(0);
513
+ });
514
+
515
+ setTimeout(() => {
516
+ log(LOG_LEVELS.ERROR, 'Force shutdown - server did not close in time');
517
+ process.exit(1);
518
+ }, 10000);
519
+ }
520
+
521
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
522
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
523
+
524
+ process.on('uncaughtException', (error) => {
525
+ log(LOG_LEVELS.ERROR, 'Uncaught exception', { error: error.message });
526
+ gracefulShutdown('uncaughtException');
527
+ });
528
+
529
+ process.on('unhandledRejection', (reason) => {
530
+ log(LOG_LEVELS.ERROR, 'Unhandled promise rejection', { reason: reason?.toString() });
531
+ gracefulShutdown('unhandledRejection');
532
+ });
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@rashidazarang/airtable-mcp",
3
- "version": "1.6.0",
4
- "description": "Airtable MCP for Claude Desktop - Connect directly to Airtable using natural language",
5
- "main": "airtable_simple.js",
3
+ "version": "2.1.1",
4
+ "description": "Airtable MCP server for Claude Desktop - Connect directly to Airtable using natural language",
5
+ "main": "airtable_simple_production.js",
6
6
  "bin": {
7
- "airtable-mcp": "./airtable_simple.js"
7
+ "airtable-mcp": "./airtable_simple_production.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node airtable_simple.js",
10
+ "start": "node airtable_simple_production.js",
11
11
  "start:python": "python3.10 inspector_server.py",
12
12
  "test": "node test_mcp_comprehensive.js",
13
13
  "test:quick": "./quick_test.sh",
14
- "dev": "node airtable_simple.js --token YOUR_TOKEN --base YOUR_BASE_ID"
14
+ "dev": "node airtable_simple_production.js --token YOUR_TOKEN --base YOUR_BASE_ID"
15
15
  },
16
16
  "keywords": [
17
17
  "airtable",
@@ -22,6 +22,15 @@
22
22
  "ai",
23
23
  "database"
24
24
  ],
25
+ "files": [
26
+ "airtable_simple.js",
27
+ "airtable_simple_production.js",
28
+ "README.md",
29
+ "LICENSE",
30
+ "package.json",
31
+ "examples/",
32
+ "bin/"
33
+ ],
25
34
  "author": "Rashid Azarang",
26
35
  "license": "MIT",
27
36
  "dependencies": {
@@ -1,12 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(cat:*)",
5
- "WebFetch(domain:github.com)",
6
- "Bash(# Test create with simpler extraction\necho \"\"TEST: CREATE AND EXTRACT ID\"\"\ncurl -s -X POST http://localhost:8010/mcp \\\n -H \"\"Content-Type: application/json\"\" \\\n -d ''{\"\"jsonrpc\"\": \"\"2.0\"\", \"\"id\"\": 2, \"\"method\"\": \"\"tools/call\"\", \"\"params\"\": {\"\"name\"\": \"\"create_record\"\", \"\"arguments\"\": {\"\"table\"\": \"\"tblH7TnJxYpNqhQYK\"\", \"\"fields\"\": {\"\"Name\"\": \"\"Simple Test\"\"}}}}'' \\\n > /tmp/raw_response.txt\n\n# Extract record ID using grep\nRECORD_ID=$(cat /tmp/raw_response.txt | grep -o ''rec[a-zA-Z0-9]\\{10,20\\}'' | head -1)\necho \"\"Extracted ID: $RECORD_ID\"\"\n\nif [ ! -z \"\"$RECORD_ID\"\" ]; then\n echo \"\"✅ Got record ID: $RECORD_ID\"\"\nelse\n echo \"\"❌ Failed to extract ID\"\"\n cat /tmp/raw_response.txt | od -c | head -20\nfi)",
7
- "Bash(# Add debug logging to see the exact API call\nexport LOG_LEVEL=DEBUG\npkill -f \"\"node.*airtable\"\" 2>/dev/null; sleep 1\nexport AIRTABLE_TOKEN=\"\"patSDGN40NJd9G8E4.9fee82826ee482d6556480d004592e5f806bd8e95cef18fc5ba2d7fc55274367\"\"\nexport AIRTABLE_BASE_ID=\"\"appTV04Fyu1Gvbunq\"\"\nnode airtable_simple.js > /tmp/server_debug.log 2>&1 &\npid=$!\nsleep 2\n\n# Test webhook creation with debug\ncurl -s -X POST http://localhost:8010/mcp \\\n -H \"\"Content-Type: application/json\"\" \\\n -d ''{\"\"jsonrpc\"\": \"\"2.0\"\", \"\"id\"\": 1, \"\"method\"\": \"\"tools/call\"\", \"\"params\"\": {\"\"name\"\": \"\"create_webhook\"\", \"\"arguments\"\": {\"\"notificationUrl\"\": \"\"https://webhook.site/debug-test\"\"}}}'' > /dev/null\n\n# Check debug log\necho \"\"Debug output:\"\"\ngrep -A 2 \"\"API Request.*webhook\"\" /tmp/server_debug.log | tail -5)",
8
- "Bash(./test_all_features.sh:*)"
9
- ],
10
- "deny": []
11
- }
12
- }
@@ -1,38 +0,0 @@
1
- ---
2
- name: Bug report
3
- about: Create a report to help us improve
4
- title: ''
5
- labels: ''
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Describe the bug**
11
- A clear and concise description of what the bug is.
12
-
13
- **To Reproduce**
14
- Steps to reproduce the behavior:
15
- 1. Go to '...'
16
- 2. Click on '....'
17
- 3. Scroll down to '....'
18
- 4. See error
19
-
20
- **Expected behavior**
21
- A clear and concise description of what you expected to happen.
22
-
23
- **Screenshots**
24
- If applicable, add screenshots to help explain your problem.
25
-
26
- **Desktop (please complete the following information):**
27
- - OS: [e.g. iOS]
28
- - Browser [e.g. chrome, safari]
29
- - Version [e.g. 22]
30
-
31
- **Smartphone (please complete the following information):**
32
- - Device: [e.g. iPhone6]
33
- - OS: [e.g. iOS8.1]
34
- - Browser [e.g. stock browser, safari]
35
- - Version [e.g. 22]
36
-
37
- **Additional context**
38
- Add any other context about the problem here.
@@ -1,10 +0,0 @@
1
- ---
2
- name: Custom issue template
3
- about: Describe this issue template's purpose here.
4
- title: ''
5
- labels: ''
6
- assignees: ''
7
-
8
- ---
9
-
10
-
@@ -1,20 +0,0 @@
1
- ---
2
- name: Feature request
3
- about: Suggest an idea for this project
4
- title: ''
5
- labels: ''
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Is your feature request related to a problem? Please describe.**
11
- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
-
13
- **Describe the solution you'd like**
14
- A clear and concise description of what you want to happen.
15
-
16
- **Describe alternatives you've considered**
17
- A clear and concise description of any alternative solutions or features you've considered.
18
-
19
- **Additional context**
20
- Add any other context or screenshots about the feature request here.