@rashidazarang/airtable-mcp 3.0.0 → 3.2.5
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/README.md +210 -43
- package/bin/airtable-mcp.js +12 -32
- package/dist/typescript/airtable-mcp-server.js +77 -0
- package/dist/typescript/airtable-mcp-server.js.map +1 -0
- package/dist/typescript/app/airtable-client.js +325 -0
- package/dist/typescript/app/airtable-client.js.map +1 -0
- package/dist/typescript/app/config.js +141 -0
- package/dist/typescript/app/config.js.map +1 -0
- package/dist/typescript/app/context.js +3 -0
- package/dist/typescript/app/context.js.map +1 -0
- package/dist/typescript/app/exceptions.js +85 -0
- package/dist/typescript/app/exceptions.js.map +1 -0
- package/dist/typescript/app/governance.js +58 -0
- package/dist/typescript/app/governance.js.map +1 -0
- package/dist/typescript/app/logger.js +47 -0
- package/dist/typescript/app/logger.js.map +1 -0
- package/dist/typescript/app/rateLimiter.js +37 -0
- package/dist/typescript/app/rateLimiter.js.map +1 -0
- package/dist/typescript/app/tools/create.js +54 -0
- package/dist/typescript/app/tools/create.js.map +1 -0
- package/dist/typescript/app/tools/describe.js +146 -0
- package/dist/typescript/app/tools/describe.js.map +1 -0
- package/dist/typescript/app/tools/handleError.js +54 -0
- package/dist/typescript/app/tools/handleError.js.map +1 -0
- package/dist/typescript/app/tools/index.js +24 -0
- package/dist/typescript/app/tools/index.js.map +1 -0
- package/dist/typescript/app/tools/listBases.js +52 -0
- package/dist/typescript/app/tools/listBases.js.map +1 -0
- package/dist/typescript/app/tools/listExceptions.js +18 -0
- package/dist/typescript/app/tools/listExceptions.js.map +1 -0
- package/dist/typescript/app/tools/listGovernance.js +17 -0
- package/dist/typescript/app/tools/listGovernance.js.map +1 -0
- package/dist/typescript/app/tools/query.js +126 -0
- package/dist/typescript/app/tools/query.js.map +1 -0
- package/dist/typescript/app/tools/update.js +56 -0
- package/dist/typescript/app/tools/update.js.map +1 -0
- package/dist/typescript/app/tools/upsert.js +65 -0
- package/dist/typescript/app/tools/upsert.js.map +1 -0
- package/dist/typescript/app/tools/webhooks.js +44 -0
- package/dist/typescript/app/tools/webhooks.js.map +1 -0
- package/dist/typescript/app/types.js +282 -0
- package/dist/typescript/app/types.js.map +1 -0
- package/dist/typescript/apps-sdk/mappers.js +70 -0
- package/dist/typescript/apps-sdk/mappers.js.map +1 -0
- package/dist/typescript/errors.js +75 -0
- package/dist/typescript/errors.js.map +1 -0
- package/dist/typescript/index.js +27 -0
- package/dist/typescript/index.js.map +1 -0
- package/package.json +63 -17
- package/tsconfig.json +44 -0
- package/types/typescript/airtable-mcp-server.d.ts +2 -0
- package/types/typescript/app/airtable-client.d.ts +49 -0
- package/types/typescript/app/config.d.ts +16 -0
- package/types/typescript/app/context.d.ts +12 -0
- package/types/typescript/app/exceptions.d.ts +12 -0
- package/types/typescript/app/governance.d.ts +18 -0
- package/types/typescript/app/logger.d.ts +13 -0
- package/types/typescript/app/rateLimiter.d.ts +13 -0
- package/types/typescript/app/tools/create.d.ts +3 -0
- package/types/typescript/app/tools/describe.d.ts +3 -0
- package/types/typescript/app/tools/handleError.d.ts +8 -0
- package/types/typescript/app/tools/index.d.ts +3 -0
- package/types/typescript/app/tools/listBases.d.ts +33 -0
- package/types/typescript/app/tools/listExceptions.d.ts +3 -0
- package/types/typescript/app/tools/listGovernance.d.ts +3 -0
- package/types/typescript/app/tools/query.d.ts +3 -0
- package/types/typescript/app/tools/update.d.ts +3 -0
- package/types/typescript/app/tools/upsert.d.ts +3 -0
- package/types/typescript/app/tools/webhooks.d.ts +3 -0
- package/types/typescript/app/types.d.ts +830 -0
- package/types/typescript/apps-sdk/mappers.d.ts +53 -0
- package/types/typescript/errors.d.ts +55 -0
- package/types/typescript/index.d.ts +10 -0
- package/types/typescript/prompt-templates.d.ts +5 -0
- package/types/typescript/test-suite.d.ts +33 -0
- package/types/typescript/tools-schemas.d.ts +5 -0
- package/airtable_simple.js +0 -1561
- package/airtable_simple_production.js +0 -1564
- package/examples/airtable-crud-example.js +0 -203
- package/examples/building-mcp.md +0 -6666
- package/examples/claude_config.json +0 -4
- package/examples/claude_simple_config.json +0 -7
- package/examples/env-demo.js +0 -172
- package/examples/example-tasks-update.json +0 -23
- package/examples/example-tasks.json +0 -26
- package/examples/example_usage.md +0 -124
- package/examples/python_debug_patch.txt +0 -27
- package/examples/sample-transform.js +0 -76
- package/examples/windsurf_mcp_config.json +0 -17
package/airtable_simple.js
DELETED
|
@@ -1,1561 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const http = require('http');
|
|
4
|
-
const https = require('https');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
|
|
8
|
-
// Load environment variables from .env file if it exists
|
|
9
|
-
const envPath = path.join(__dirname, '.env');
|
|
10
|
-
if (fs.existsSync(envPath)) {
|
|
11
|
-
require('dotenv').config({ path: envPath });
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Parse command line arguments with environment variable fallback
|
|
15
|
-
const args = process.argv.slice(2);
|
|
16
|
-
let tokenIndex = args.indexOf('--token');
|
|
17
|
-
let baseIndex = args.indexOf('--base');
|
|
18
|
-
|
|
19
|
-
// Use environment variables as fallback
|
|
20
|
-
const token = tokenIndex !== -1 ? args[tokenIndex + 1] : process.env.AIRTABLE_TOKEN || process.env.AIRTABLE_API_TOKEN;
|
|
21
|
-
const baseId = baseIndex !== -1 ? args[baseIndex + 1] : process.env.AIRTABLE_BASE_ID || process.env.AIRTABLE_BASE;
|
|
22
|
-
|
|
23
|
-
if (!token || !baseId) {
|
|
24
|
-
console.error('Error: Missing Airtable credentials');
|
|
25
|
-
console.error('\nUsage options:');
|
|
26
|
-
console.error(' 1. Command line: node airtable_enhanced.js --token YOUR_TOKEN --base YOUR_BASE_ID');
|
|
27
|
-
console.error(' 2. Environment variables: AIRTABLE_TOKEN and AIRTABLE_BASE_ID');
|
|
28
|
-
console.error(' 3. .env file with AIRTABLE_TOKEN and AIRTABLE_BASE_ID');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Configure logging levels
|
|
33
|
-
const LOG_LEVELS = {
|
|
34
|
-
ERROR: 0,
|
|
35
|
-
WARN: 1,
|
|
36
|
-
INFO: 2,
|
|
37
|
-
DEBUG: 3
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const currentLogLevel = process.env.LOG_LEVEL ? LOG_LEVELS[process.env.LOG_LEVEL.toUpperCase()] || LOG_LEVELS.INFO : LOG_LEVELS.INFO;
|
|
41
|
-
|
|
42
|
-
function log(level, message, ...args) {
|
|
43
|
-
const levelName = Object.keys(LOG_LEVELS).find(key => LOG_LEVELS[key] === level);
|
|
44
|
-
const timestamp = new Date().toISOString();
|
|
45
|
-
|
|
46
|
-
if (level <= currentLogLevel) {
|
|
47
|
-
const prefix = `[${timestamp}] [${levelName}]`;
|
|
48
|
-
if (level === LOG_LEVELS.ERROR) {
|
|
49
|
-
console.error(prefix, message, ...args);
|
|
50
|
-
} else if (level === LOG_LEVELS.WARN) {
|
|
51
|
-
console.warn(prefix, message, ...args);
|
|
52
|
-
} else {
|
|
53
|
-
console.log(prefix, message, ...args);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
log(LOG_LEVELS.INFO, `Starting Enhanced Airtable MCP server v1.6.0`);
|
|
59
|
-
log(LOG_LEVELS.INFO, `Authentication configured`);
|
|
60
|
-
log(LOG_LEVELS.INFO, `Base connection established`);
|
|
61
|
-
|
|
62
|
-
// Enhanced Airtable API function with full HTTP method support
|
|
63
|
-
function callAirtableAPI(endpoint, method = 'GET', body = null, queryParams = {}) {
|
|
64
|
-
return new Promise((resolve, reject) => {
|
|
65
|
-
const isBaseEndpoint = !endpoint.startsWith('meta/') && !endpoint.startsWith('bases/');
|
|
66
|
-
const baseUrl = isBaseEndpoint ? `${baseId}/${endpoint}` : endpoint;
|
|
67
|
-
|
|
68
|
-
// Build query string
|
|
69
|
-
const queryString = Object.keys(queryParams).length > 0
|
|
70
|
-
? '?' + new URLSearchParams(queryParams).toString()
|
|
71
|
-
: '';
|
|
72
|
-
|
|
73
|
-
const url = `https://api.airtable.com/v0/${baseUrl}${queryString}`;
|
|
74
|
-
const urlObj = new URL(url);
|
|
75
|
-
|
|
76
|
-
log(LOG_LEVELS.DEBUG, `API Request: ${method} ${url}`);
|
|
77
|
-
if (body) {
|
|
78
|
-
log(LOG_LEVELS.DEBUG, `Request body:`, JSON.stringify(body, null, 2));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const options = {
|
|
82
|
-
hostname: urlObj.hostname,
|
|
83
|
-
path: urlObj.pathname + urlObj.search,
|
|
84
|
-
method: method,
|
|
85
|
-
headers: {
|
|
86
|
-
'Authorization': `Bearer ${token}`,
|
|
87
|
-
'Content-Type': 'application/json'
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const req = https.request(options, (response) => {
|
|
92
|
-
let data = '';
|
|
93
|
-
|
|
94
|
-
response.on('data', (chunk) => {
|
|
95
|
-
data += chunk;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
response.on('end', () => {
|
|
99
|
-
log(LOG_LEVELS.DEBUG, `Response status: ${response.statusCode}`);
|
|
100
|
-
log(LOG_LEVELS.DEBUG, `Response data:`, data);
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const parsed = data ? JSON.parse(data) : {};
|
|
104
|
-
|
|
105
|
-
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
106
|
-
resolve(parsed);
|
|
107
|
-
} else {
|
|
108
|
-
const error = parsed.error || {};
|
|
109
|
-
reject(new Error(`Airtable API error (${response.statusCode}): ${error.message || error.type || 'Unknown error'}`));
|
|
110
|
-
}
|
|
111
|
-
} catch (e) {
|
|
112
|
-
reject(new Error(`Failed to parse Airtable response: ${e.message}`));
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
req.on('error', (error) => {
|
|
118
|
-
reject(new Error(`Airtable API request failed: ${error.message}`));
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
if (body) {
|
|
122
|
-
req.write(JSON.stringify(body));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
req.end();
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Create HTTP server
|
|
130
|
-
const server = http.createServer(async (req, res) => {
|
|
131
|
-
// Enable CORS
|
|
132
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
133
|
-
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
134
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
135
|
-
|
|
136
|
-
// Handle preflight request
|
|
137
|
-
if (req.method === 'OPTIONS') {
|
|
138
|
-
res.writeHead(200);
|
|
139
|
-
res.end();
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Only handle POST requests to /mcp
|
|
144
|
-
if (req.method !== 'POST' || !req.url.endsWith('/mcp')) {
|
|
145
|
-
res.writeHead(404);
|
|
146
|
-
res.end();
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
let body = '';
|
|
151
|
-
req.on('data', chunk => {
|
|
152
|
-
body += chunk.toString();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
req.on('end', async () => {
|
|
156
|
-
try {
|
|
157
|
-
const request = JSON.parse(body);
|
|
158
|
-
log(LOG_LEVELS.DEBUG, 'Received request:', JSON.stringify(request, null, 2));
|
|
159
|
-
|
|
160
|
-
// Handle JSON-RPC methods
|
|
161
|
-
if (request.method === 'tools/list') {
|
|
162
|
-
const response = {
|
|
163
|
-
jsonrpc: '2.0',
|
|
164
|
-
id: request.id,
|
|
165
|
-
result: {
|
|
166
|
-
tools: [
|
|
167
|
-
{
|
|
168
|
-
name: 'list_tables',
|
|
169
|
-
description: 'List all tables in the Airtable base',
|
|
170
|
-
inputSchema: {
|
|
171
|
-
type: 'object',
|
|
172
|
-
properties: {}
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
name: 'list_records',
|
|
177
|
-
description: 'List records from a specific table',
|
|
178
|
-
inputSchema: {
|
|
179
|
-
type: 'object',
|
|
180
|
-
properties: {
|
|
181
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
182
|
-
maxRecords: { type: 'number', description: 'Maximum number of records to return' },
|
|
183
|
-
view: { type: 'string', description: 'View name or ID' }
|
|
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
|
-
name: 'search_records',
|
|
239
|
-
description: 'Search records with filtering and sorting',
|
|
240
|
-
inputSchema: {
|
|
241
|
-
type: 'object',
|
|
242
|
-
properties: {
|
|
243
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
244
|
-
filterByFormula: { type: 'string', description: 'Airtable formula to filter records' },
|
|
245
|
-
sort: { type: 'array', description: 'Sort configuration' },
|
|
246
|
-
maxRecords: { type: 'number', description: 'Maximum records to return' },
|
|
247
|
-
fields: { type: 'array', description: 'Fields to return' }
|
|
248
|
-
},
|
|
249
|
-
required: ['table']
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: 'list_webhooks',
|
|
254
|
-
description: 'List all webhooks for the base',
|
|
255
|
-
inputSchema: {
|
|
256
|
-
type: 'object',
|
|
257
|
-
properties: {}
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: 'create_webhook',
|
|
262
|
-
description: 'Create a new webhook for a table',
|
|
263
|
-
inputSchema: {
|
|
264
|
-
type: 'object',
|
|
265
|
-
properties: {
|
|
266
|
-
notificationUrl: { type: 'string', description: 'URL to receive webhook notifications' },
|
|
267
|
-
specification: {
|
|
268
|
-
type: 'object',
|
|
269
|
-
description: 'Webhook specification',
|
|
270
|
-
properties: {
|
|
271
|
-
options: {
|
|
272
|
-
type: 'object',
|
|
273
|
-
properties: {
|
|
274
|
-
filters: {
|
|
275
|
-
type: 'object',
|
|
276
|
-
properties: {
|
|
277
|
-
dataTypes: { type: 'array', items: { type: 'string' } },
|
|
278
|
-
recordChangeScope: { type: 'string' }
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
required: ['notificationUrl']
|
|
287
|
-
}
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
name: 'delete_webhook',
|
|
291
|
-
description: 'Delete a webhook',
|
|
292
|
-
inputSchema: {
|
|
293
|
-
type: 'object',
|
|
294
|
-
properties: {
|
|
295
|
-
webhookId: { type: 'string', description: 'Webhook ID to delete' }
|
|
296
|
-
},
|
|
297
|
-
required: ['webhookId']
|
|
298
|
-
}
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
name: 'get_webhook_payloads',
|
|
302
|
-
description: 'Get webhook payload history',
|
|
303
|
-
inputSchema: {
|
|
304
|
-
type: 'object',
|
|
305
|
-
properties: {
|
|
306
|
-
webhookId: { type: 'string', description: 'Webhook ID' },
|
|
307
|
-
cursor: { type: 'number', description: 'Cursor for pagination' }
|
|
308
|
-
},
|
|
309
|
-
required: ['webhookId']
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: 'refresh_webhook',
|
|
314
|
-
description: 'Refresh a webhook to extend its expiration',
|
|
315
|
-
inputSchema: {
|
|
316
|
-
type: 'object',
|
|
317
|
-
properties: {
|
|
318
|
-
webhookId: { type: 'string', description: 'Webhook ID to refresh' }
|
|
319
|
-
},
|
|
320
|
-
required: ['webhookId']
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
name: 'list_bases',
|
|
325
|
-
description: 'List all accessible Airtable bases',
|
|
326
|
-
inputSchema: {
|
|
327
|
-
type: 'object',
|
|
328
|
-
properties: {
|
|
329
|
-
offset: { type: 'string', description: 'Pagination offset for listing more bases' }
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: 'get_base_schema',
|
|
335
|
-
description: 'Get complete schema information for a base',
|
|
336
|
-
inputSchema: {
|
|
337
|
-
type: 'object',
|
|
338
|
-
properties: {
|
|
339
|
-
baseId: { type: 'string', description: 'Base ID to get schema for (optional, defaults to current base)' }
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
name: 'describe_table',
|
|
345
|
-
description: 'Get detailed information about a specific table including all fields',
|
|
346
|
-
inputSchema: {
|
|
347
|
-
type: 'object',
|
|
348
|
-
properties: {
|
|
349
|
-
table: { type: 'string', description: 'Table name or ID' }
|
|
350
|
-
},
|
|
351
|
-
required: ['table']
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
{
|
|
355
|
-
name: 'create_table',
|
|
356
|
-
description: 'Create a new table in the base',
|
|
357
|
-
inputSchema: {
|
|
358
|
-
type: 'object',
|
|
359
|
-
properties: {
|
|
360
|
-
name: { type: 'string', description: 'Name for the new table' },
|
|
361
|
-
description: { type: 'string', description: 'Optional description for the table' },
|
|
362
|
-
fields: {
|
|
363
|
-
type: 'array',
|
|
364
|
-
description: 'Array of field definitions',
|
|
365
|
-
items: {
|
|
366
|
-
type: 'object',
|
|
367
|
-
properties: {
|
|
368
|
-
name: { type: 'string', description: 'Field name' },
|
|
369
|
-
type: { type: 'string', description: 'Field type (singleLineText, number, etc.)' },
|
|
370
|
-
description: { type: 'string', description: 'Field description' },
|
|
371
|
-
options: { type: 'object', description: 'Field-specific options' }
|
|
372
|
-
},
|
|
373
|
-
required: ['name', 'type']
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
required: ['name', 'fields']
|
|
378
|
-
}
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
name: 'update_table',
|
|
382
|
-
description: 'Update table name or description',
|
|
383
|
-
inputSchema: {
|
|
384
|
-
type: 'object',
|
|
385
|
-
properties: {
|
|
386
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
387
|
-
name: { type: 'string', description: 'New table name' },
|
|
388
|
-
description: { type: 'string', description: 'New table description' }
|
|
389
|
-
},
|
|
390
|
-
required: ['table']
|
|
391
|
-
}
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
name: 'delete_table',
|
|
395
|
-
description: 'Delete a table (WARNING: This will permanently delete all data)',
|
|
396
|
-
inputSchema: {
|
|
397
|
-
type: 'object',
|
|
398
|
-
properties: {
|
|
399
|
-
table: { type: 'string', description: 'Table name or ID to delete' },
|
|
400
|
-
confirm: { type: 'boolean', description: 'Must be true to confirm deletion' }
|
|
401
|
-
},
|
|
402
|
-
required: ['table', 'confirm']
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
name: 'create_field',
|
|
407
|
-
description: 'Add a new field to an existing table',
|
|
408
|
-
inputSchema: {
|
|
409
|
-
type: 'object',
|
|
410
|
-
properties: {
|
|
411
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
412
|
-
name: { type: 'string', description: 'Field name' },
|
|
413
|
-
type: { type: 'string', description: 'Field type (singleLineText, number, multipleSelectionList, etc.)' },
|
|
414
|
-
description: { type: 'string', description: 'Field description' },
|
|
415
|
-
options: { type: 'object', description: 'Field-specific options (e.g., choices for select fields)' }
|
|
416
|
-
},
|
|
417
|
-
required: ['table', 'name', 'type']
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
name: 'update_field',
|
|
422
|
-
description: 'Update field properties',
|
|
423
|
-
inputSchema: {
|
|
424
|
-
type: 'object',
|
|
425
|
-
properties: {
|
|
426
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
427
|
-
fieldId: { type: 'string', description: 'Field ID to update' },
|
|
428
|
-
name: { type: 'string', description: 'New field name' },
|
|
429
|
-
description: { type: 'string', description: 'New field description' },
|
|
430
|
-
options: { type: 'object', description: 'Updated field options' }
|
|
431
|
-
},
|
|
432
|
-
required: ['table', 'fieldId']
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: 'delete_field',
|
|
437
|
-
description: 'Delete a field from a table (WARNING: This will permanently delete all data in this field)',
|
|
438
|
-
inputSchema: {
|
|
439
|
-
type: 'object',
|
|
440
|
-
properties: {
|
|
441
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
442
|
-
fieldId: { type: 'string', description: 'Field ID to delete' },
|
|
443
|
-
confirm: { type: 'boolean', description: 'Must be true to confirm deletion' }
|
|
444
|
-
},
|
|
445
|
-
required: ['table', 'fieldId', 'confirm']
|
|
446
|
-
}
|
|
447
|
-
},
|
|
448
|
-
{
|
|
449
|
-
name: 'list_field_types',
|
|
450
|
-
description: 'Get a reference of all available Airtable field types and their schemas',
|
|
451
|
-
inputSchema: {
|
|
452
|
-
type: 'object',
|
|
453
|
-
properties: {}
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
name: 'get_table_views',
|
|
458
|
-
description: 'List all views for a specific table',
|
|
459
|
-
inputSchema: {
|
|
460
|
-
type: 'object',
|
|
461
|
-
properties: {
|
|
462
|
-
table: { type: 'string', description: 'Table name or ID' }
|
|
463
|
-
},
|
|
464
|
-
required: ['table']
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
name: 'upload_attachment',
|
|
469
|
-
description: 'Upload/attach a file from URL to a record',
|
|
470
|
-
inputSchema: {
|
|
471
|
-
type: 'object',
|
|
472
|
-
properties: {
|
|
473
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
474
|
-
recordId: { type: 'string', description: 'Record ID to attach file to' },
|
|
475
|
-
fieldName: { type: 'string', description: 'Name of the attachment field' },
|
|
476
|
-
url: { type: 'string', description: 'Public URL of the file to attach' },
|
|
477
|
-
filename: { type: 'string', description: 'Optional filename for the attachment' }
|
|
478
|
-
},
|
|
479
|
-
required: ['table', 'recordId', 'fieldName', 'url']
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
name: 'batch_create_records',
|
|
484
|
-
description: 'Create multiple records at once (up to 10)',
|
|
485
|
-
inputSchema: {
|
|
486
|
-
type: 'object',
|
|
487
|
-
properties: {
|
|
488
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
489
|
-
records: {
|
|
490
|
-
type: 'array',
|
|
491
|
-
description: 'Array of record objects to create (max 10)',
|
|
492
|
-
items: {
|
|
493
|
-
type: 'object',
|
|
494
|
-
properties: {
|
|
495
|
-
fields: { type: 'object', description: 'Record fields' }
|
|
496
|
-
},
|
|
497
|
-
required: ['fields']
|
|
498
|
-
},
|
|
499
|
-
maxItems: 10
|
|
500
|
-
}
|
|
501
|
-
},
|
|
502
|
-
required: ['table', 'records']
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
name: 'batch_update_records',
|
|
507
|
-
description: 'Update multiple records at once (up to 10)',
|
|
508
|
-
inputSchema: {
|
|
509
|
-
type: 'object',
|
|
510
|
-
properties: {
|
|
511
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
512
|
-
records: {
|
|
513
|
-
type: 'array',
|
|
514
|
-
description: 'Array of record objects to update (max 10)',
|
|
515
|
-
items: {
|
|
516
|
-
type: 'object',
|
|
517
|
-
properties: {
|
|
518
|
-
id: { type: 'string', description: 'Record ID' },
|
|
519
|
-
fields: { type: 'object', description: 'Fields to update' }
|
|
520
|
-
},
|
|
521
|
-
required: ['id', 'fields']
|
|
522
|
-
},
|
|
523
|
-
maxItems: 10
|
|
524
|
-
}
|
|
525
|
-
},
|
|
526
|
-
required: ['table', 'records']
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
name: 'batch_delete_records',
|
|
531
|
-
description: 'Delete multiple records at once (up to 10)',
|
|
532
|
-
inputSchema: {
|
|
533
|
-
type: 'object',
|
|
534
|
-
properties: {
|
|
535
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
536
|
-
recordIds: {
|
|
537
|
-
type: 'array',
|
|
538
|
-
description: 'Array of record IDs to delete (max 10)',
|
|
539
|
-
items: { type: 'string' },
|
|
540
|
-
maxItems: 10
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
required: ['table', 'recordIds']
|
|
544
|
-
}
|
|
545
|
-
},
|
|
546
|
-
{
|
|
547
|
-
name: 'batch_upsert_records',
|
|
548
|
-
description: 'Update existing records or create new ones based on key fields',
|
|
549
|
-
inputSchema: {
|
|
550
|
-
type: 'object',
|
|
551
|
-
properties: {
|
|
552
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
553
|
-
records: {
|
|
554
|
-
type: 'array',
|
|
555
|
-
description: 'Array of record objects (max 10)',
|
|
556
|
-
items: {
|
|
557
|
-
type: 'object',
|
|
558
|
-
properties: {
|
|
559
|
-
fields: { type: 'object', description: 'Record fields' }
|
|
560
|
-
},
|
|
561
|
-
required: ['fields']
|
|
562
|
-
},
|
|
563
|
-
maxItems: 10
|
|
564
|
-
},
|
|
565
|
-
keyFields: {
|
|
566
|
-
type: 'array',
|
|
567
|
-
description: 'Fields to use for matching existing records',
|
|
568
|
-
items: { type: 'string' }
|
|
569
|
-
}
|
|
570
|
-
},
|
|
571
|
-
required: ['table', 'records', 'keyFields']
|
|
572
|
-
}
|
|
573
|
-
},
|
|
574
|
-
{
|
|
575
|
-
name: 'create_view',
|
|
576
|
-
description: 'Create a new view for a table',
|
|
577
|
-
inputSchema: {
|
|
578
|
-
type: 'object',
|
|
579
|
-
properties: {
|
|
580
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
581
|
-
name: { type: 'string', description: 'Name for the new view' },
|
|
582
|
-
type: { type: 'string', description: 'View type (grid, form, calendar, etc.)', enum: ['grid', 'form', 'calendar', 'gallery', 'kanban', 'timeline', 'gantt'] },
|
|
583
|
-
visibleFieldIds: { type: 'array', description: 'Array of field IDs to show in view', items: { type: 'string' } },
|
|
584
|
-
fieldOrder: { type: 'array', description: 'Order of fields in view', items: { type: 'string' } }
|
|
585
|
-
},
|
|
586
|
-
required: ['table', 'name', 'type']
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
name: 'get_view_metadata',
|
|
591
|
-
description: 'Get detailed metadata for a specific view',
|
|
592
|
-
inputSchema: {
|
|
593
|
-
type: 'object',
|
|
594
|
-
properties: {
|
|
595
|
-
table: { type: 'string', description: 'Table name or ID' },
|
|
596
|
-
viewId: { type: 'string', description: 'View ID' }
|
|
597
|
-
},
|
|
598
|
-
required: ['table', 'viewId']
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
name: 'create_base',
|
|
603
|
-
description: 'Create a new Airtable base',
|
|
604
|
-
inputSchema: {
|
|
605
|
-
type: 'object',
|
|
606
|
-
properties: {
|
|
607
|
-
name: { type: 'string', description: 'Name for the new base' },
|
|
608
|
-
workspaceId: { type: 'string', description: 'Workspace ID to create the base in' },
|
|
609
|
-
tables: {
|
|
610
|
-
type: 'array',
|
|
611
|
-
description: 'Initial tables to create in the base',
|
|
612
|
-
items: {
|
|
613
|
-
type: 'object',
|
|
614
|
-
properties: {
|
|
615
|
-
name: { type: 'string', description: 'Table name' },
|
|
616
|
-
description: { type: 'string', description: 'Table description' },
|
|
617
|
-
fields: {
|
|
618
|
-
type: 'array',
|
|
619
|
-
description: 'Table fields',
|
|
620
|
-
items: {
|
|
621
|
-
type: 'object',
|
|
622
|
-
properties: {
|
|
623
|
-
name: { type: 'string', description: 'Field name' },
|
|
624
|
-
type: { type: 'string', description: 'Field type' }
|
|
625
|
-
},
|
|
626
|
-
required: ['name', 'type']
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
},
|
|
630
|
-
required: ['name', 'fields']
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
required: ['name', 'tables']
|
|
635
|
-
}
|
|
636
|
-
},
|
|
637
|
-
{
|
|
638
|
-
name: 'list_collaborators',
|
|
639
|
-
description: 'List collaborators and their permissions for the current base',
|
|
640
|
-
inputSchema: {
|
|
641
|
-
type: 'object',
|
|
642
|
-
properties: {
|
|
643
|
-
baseId: { type: 'string', description: 'Base ID (optional, defaults to current base)' }
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
name: 'list_shares',
|
|
649
|
-
description: 'List shared views and their configurations',
|
|
650
|
-
inputSchema: {
|
|
651
|
-
type: 'object',
|
|
652
|
-
properties: {
|
|
653
|
-
baseId: { type: 'string', description: 'Base ID (optional, defaults to current base)' }
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
]
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
661
|
-
res.end(JSON.stringify(response));
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
if (request.method === 'resources/list') {
|
|
666
|
-
const response = {
|
|
667
|
-
jsonrpc: '2.0',
|
|
668
|
-
id: request.id,
|
|
669
|
-
result: {
|
|
670
|
-
resources: [
|
|
671
|
-
{
|
|
672
|
-
id: 'airtable_tables',
|
|
673
|
-
name: 'Airtable Tables',
|
|
674
|
-
description: 'Tables in your Airtable base'
|
|
675
|
-
}
|
|
676
|
-
]
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
680
|
-
res.end(JSON.stringify(response));
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
if (request.method === 'prompts/list') {
|
|
685
|
-
const response = {
|
|
686
|
-
jsonrpc: '2.0',
|
|
687
|
-
id: request.id,
|
|
688
|
-
result: {
|
|
689
|
-
prompts: [
|
|
690
|
-
{
|
|
691
|
-
id: 'tables_prompt',
|
|
692
|
-
name: 'List Tables',
|
|
693
|
-
description: 'List all tables'
|
|
694
|
-
}
|
|
695
|
-
]
|
|
696
|
-
}
|
|
697
|
-
};
|
|
698
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
699
|
-
res.end(JSON.stringify(response));
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Handle tool calls
|
|
704
|
-
if (request.method === 'tools/call') {
|
|
705
|
-
const toolName = request.params.name;
|
|
706
|
-
const toolParams = request.params.arguments || {};
|
|
707
|
-
|
|
708
|
-
let result;
|
|
709
|
-
let responseText;
|
|
710
|
-
|
|
711
|
-
try {
|
|
712
|
-
// LIST TABLES
|
|
713
|
-
if (toolName === 'list_tables') {
|
|
714
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables`);
|
|
715
|
-
const tables = result.tables || [];
|
|
716
|
-
|
|
717
|
-
responseText = tables.length > 0
|
|
718
|
-
? `Found ${tables.length} table(s):\n` + tables.map((table, i) =>
|
|
719
|
-
`${i+1}. ${table.name} (ID: ${table.id}, Fields: ${table.fields?.length || 0})`
|
|
720
|
-
).join('\n')
|
|
721
|
-
: 'No tables found in this base.';
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// LIST RECORDS
|
|
725
|
-
else if (toolName === 'list_records') {
|
|
726
|
-
const { table, maxRecords, view } = toolParams;
|
|
727
|
-
|
|
728
|
-
const queryParams = {};
|
|
729
|
-
if (maxRecords) queryParams.maxRecords = maxRecords;
|
|
730
|
-
if (view) queryParams.view = view;
|
|
731
|
-
|
|
732
|
-
result = await callAirtableAPI(`${table}`, 'GET', null, queryParams);
|
|
733
|
-
const records = result.records || [];
|
|
734
|
-
|
|
735
|
-
responseText = records.length > 0
|
|
736
|
-
? `Found ${records.length} record(s) in table "${table}":\n` +
|
|
737
|
-
records.map((record, i) =>
|
|
738
|
-
`${i+1}. ID: ${record.id}\n Fields: ${JSON.stringify(record.fields, null, 2)}`
|
|
739
|
-
).join('\n\n')
|
|
740
|
-
: `No records found in table "${table}".`;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// GET SINGLE RECORD
|
|
744
|
-
else if (toolName === 'get_record') {
|
|
745
|
-
const { table, recordId } = toolParams;
|
|
746
|
-
|
|
747
|
-
result = await callAirtableAPI(`${table}/${recordId}`);
|
|
748
|
-
|
|
749
|
-
responseText = `Record ${recordId} from table "${table}":\n` +
|
|
750
|
-
JSON.stringify(result.fields, null, 2) +
|
|
751
|
-
`\n\nCreated: ${result.createdTime}`;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// CREATE RECORD
|
|
755
|
-
else if (toolName === 'create_record') {
|
|
756
|
-
const { table, fields } = toolParams;
|
|
757
|
-
|
|
758
|
-
const body = {
|
|
759
|
-
fields: fields
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
result = await callAirtableAPI(table, 'POST', body);
|
|
763
|
-
|
|
764
|
-
responseText = `Successfully created record in table "${table}":\n` +
|
|
765
|
-
`Record ID: ${result.id}\n` +
|
|
766
|
-
`Fields: ${JSON.stringify(result.fields, null, 2)}\n` +
|
|
767
|
-
`Created at: ${result.createdTime}`;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
// UPDATE RECORD
|
|
771
|
-
else if (toolName === 'update_record') {
|
|
772
|
-
const { table, recordId, fields } = toolParams;
|
|
773
|
-
|
|
774
|
-
const body = {
|
|
775
|
-
fields: fields
|
|
776
|
-
};
|
|
777
|
-
|
|
778
|
-
result = await callAirtableAPI(`${table}/${recordId}`, 'PATCH', body);
|
|
779
|
-
|
|
780
|
-
responseText = `Successfully updated record ${recordId} in table "${table}":\n` +
|
|
781
|
-
`Updated fields: ${JSON.stringify(result.fields, null, 2)}`;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// DELETE RECORD
|
|
785
|
-
else if (toolName === 'delete_record') {
|
|
786
|
-
const { table, recordId } = toolParams;
|
|
787
|
-
|
|
788
|
-
result = await callAirtableAPI(`${table}/${recordId}`, 'DELETE');
|
|
789
|
-
|
|
790
|
-
responseText = `Successfully deleted record ${recordId} from table "${table}".\n` +
|
|
791
|
-
`Deleted record ID: ${result.id}\n` +
|
|
792
|
-
`Deleted: ${result.deleted}`;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
// SEARCH RECORDS
|
|
796
|
-
else if (toolName === 'search_records') {
|
|
797
|
-
const { table, filterByFormula, sort, maxRecords, fields } = toolParams;
|
|
798
|
-
|
|
799
|
-
const queryParams = {};
|
|
800
|
-
if (filterByFormula) queryParams.filterByFormula = filterByFormula;
|
|
801
|
-
if (maxRecords) queryParams.maxRecords = maxRecords;
|
|
802
|
-
if (fields && fields.length > 0) queryParams.fields = fields;
|
|
803
|
-
if (sort && sort.length > 0) {
|
|
804
|
-
sort.forEach((s, i) => {
|
|
805
|
-
queryParams[`sort[${i}][field]`] = s.field;
|
|
806
|
-
queryParams[`sort[${i}][direction]`] = s.direction || 'asc';
|
|
807
|
-
});
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
result = await callAirtableAPI(table, 'GET', null, queryParams);
|
|
811
|
-
const records = result.records || [];
|
|
812
|
-
|
|
813
|
-
responseText = records.length > 0
|
|
814
|
-
? `Found ${records.length} matching record(s) in table "${table}":\n` +
|
|
815
|
-
records.map((record, i) =>
|
|
816
|
-
`${i+1}. ID: ${record.id}\n Fields: ${JSON.stringify(record.fields, null, 2)}`
|
|
817
|
-
).join('\n\n')
|
|
818
|
-
: `No records found matching the search criteria in table "${table}".`;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// LIST WEBHOOKS
|
|
822
|
-
else if (toolName === 'list_webhooks') {
|
|
823
|
-
result = await callAirtableAPI(`bases/${baseId}/webhooks`, 'GET');
|
|
824
|
-
const webhooks = result.webhooks || [];
|
|
825
|
-
|
|
826
|
-
responseText = webhooks.length > 0
|
|
827
|
-
? `Found ${webhooks.length} webhook(s):\n` +
|
|
828
|
-
webhooks.map((webhook, i) =>
|
|
829
|
-
`${i+1}. ID: ${webhook.id}\n` +
|
|
830
|
-
` URL: ${webhook.notificationUrl}\n` +
|
|
831
|
-
` Active: ${webhook.isHookEnabled}\n` +
|
|
832
|
-
` Created: ${webhook.createdTime}\n` +
|
|
833
|
-
` Expires: ${webhook.expirationTime}`
|
|
834
|
-
).join('\n\n')
|
|
835
|
-
: 'No webhooks configured for this base.';
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// CREATE WEBHOOK
|
|
839
|
-
else if (toolName === 'create_webhook') {
|
|
840
|
-
const { notificationUrl, specification } = toolParams;
|
|
841
|
-
|
|
842
|
-
const body = {
|
|
843
|
-
notificationUrl: notificationUrl,
|
|
844
|
-
specification: specification || {
|
|
845
|
-
options: {
|
|
846
|
-
filters: {
|
|
847
|
-
dataTypes: ['tableData']
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
result = await callAirtableAPI(`bases/${baseId}/webhooks`, 'POST', body);
|
|
854
|
-
|
|
855
|
-
responseText = `Successfully created webhook:\n` +
|
|
856
|
-
`Webhook ID: ${result.id}\n` +
|
|
857
|
-
`URL: ${result.notificationUrl}\n` +
|
|
858
|
-
`MAC Secret: ${result.macSecretBase64}\n` +
|
|
859
|
-
`Expiration: ${result.expirationTime}\n` +
|
|
860
|
-
`Cursor: ${result.cursorForNextPayload}\n\n` +
|
|
861
|
-
`⚠️ IMPORTANT: Save the MAC secret - it won't be shown again!`;
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// DELETE WEBHOOK
|
|
865
|
-
else if (toolName === 'delete_webhook') {
|
|
866
|
-
const { webhookId } = toolParams;
|
|
867
|
-
|
|
868
|
-
await callAirtableAPI(`bases/${baseId}/webhooks/${webhookId}`, 'DELETE');
|
|
869
|
-
|
|
870
|
-
responseText = `Successfully deleted webhook ${webhookId}`;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// GET WEBHOOK PAYLOADS
|
|
874
|
-
else if (toolName === 'get_webhook_payloads') {
|
|
875
|
-
const { webhookId, cursor } = toolParams;
|
|
876
|
-
|
|
877
|
-
const queryParams = {};
|
|
878
|
-
if (cursor) queryParams.cursor = cursor;
|
|
879
|
-
|
|
880
|
-
result = await callAirtableAPI(`bases/${baseId}/webhooks/${webhookId}/payloads`, 'GET', null, queryParams);
|
|
881
|
-
|
|
882
|
-
const payloads = result.payloads || [];
|
|
883
|
-
responseText = payloads.length > 0
|
|
884
|
-
? `Found ${payloads.length} webhook payload(s):\n` +
|
|
885
|
-
payloads.map((payload, i) =>
|
|
886
|
-
`${i+1}. Timestamp: ${payload.timestamp}\n` +
|
|
887
|
-
` Base/Table: ${payload.baseTransactionNumber}\n` +
|
|
888
|
-
` Change Types: ${JSON.stringify(payload.changePayload?.changedTablesById || {})}`
|
|
889
|
-
).join('\n\n') +
|
|
890
|
-
(result.cursor ? `\n\nNext cursor: ${result.cursor}` : '')
|
|
891
|
-
: 'No payloads found for this webhook.';
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// REFRESH WEBHOOK
|
|
895
|
-
else if (toolName === 'refresh_webhook') {
|
|
896
|
-
const { webhookId } = toolParams;
|
|
897
|
-
|
|
898
|
-
result = await callAirtableAPI(`bases/${baseId}/webhooks/${webhookId}/refresh`, 'POST');
|
|
899
|
-
|
|
900
|
-
responseText = `Successfully refreshed webhook ${webhookId}:\n` +
|
|
901
|
-
`New expiration: ${result.expirationTime}`;
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Schema Management Tools
|
|
905
|
-
else if (toolName === 'list_bases') {
|
|
906
|
-
const { offset } = toolParams;
|
|
907
|
-
const queryParams = offset ? { offset } : {};
|
|
908
|
-
|
|
909
|
-
result = await callAirtableAPI('meta/bases', 'GET', null, queryParams);
|
|
910
|
-
|
|
911
|
-
if (result.bases && result.bases.length > 0) {
|
|
912
|
-
responseText = `Found ${result.bases.length} accessible base(s):\n`;
|
|
913
|
-
result.bases.forEach((base, index) => {
|
|
914
|
-
responseText += `${index + 1}. ${base.name} (ID: ${base.id})\n`;
|
|
915
|
-
if (base.permissionLevel) {
|
|
916
|
-
responseText += ` Permission: ${base.permissionLevel}\n`;
|
|
917
|
-
}
|
|
918
|
-
});
|
|
919
|
-
if (result.offset) {
|
|
920
|
-
responseText += `\nNext page offset: ${result.offset}`;
|
|
921
|
-
}
|
|
922
|
-
} else {
|
|
923
|
-
responseText = 'No accessible bases found.';
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
else if (toolName === 'get_base_schema') {
|
|
928
|
-
const { baseId: targetBaseId } = toolParams;
|
|
929
|
-
const targetId = targetBaseId || baseId;
|
|
930
|
-
|
|
931
|
-
result = await callAirtableAPI(`meta/bases/${targetId}/tables`, 'GET');
|
|
932
|
-
|
|
933
|
-
if (result.tables && result.tables.length > 0) {
|
|
934
|
-
responseText = `Base schema for ${targetId}:\n\n`;
|
|
935
|
-
result.tables.forEach((table, index) => {
|
|
936
|
-
responseText += `${index + 1}. Table: ${table.name} (ID: ${table.id})\n`;
|
|
937
|
-
if (table.description) {
|
|
938
|
-
responseText += ` Description: ${table.description}\n`;
|
|
939
|
-
}
|
|
940
|
-
responseText += ` Fields (${table.fields.length}):\n`;
|
|
941
|
-
table.fields.forEach((field, fieldIndex) => {
|
|
942
|
-
responseText += ` ${fieldIndex + 1}. ${field.name} (${field.type})\n`;
|
|
943
|
-
if (field.description) {
|
|
944
|
-
responseText += ` Description: ${field.description}\n`;
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
if (table.views && table.views.length > 0) {
|
|
948
|
-
responseText += ` Views (${table.views.length}): ${table.views.map(v => v.name).join(', ')}\n`;
|
|
949
|
-
}
|
|
950
|
-
responseText += '\n';
|
|
951
|
-
});
|
|
952
|
-
} else {
|
|
953
|
-
responseText = 'No tables found in this base.';
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
else if (toolName === 'describe_table') {
|
|
958
|
-
const { table } = toolParams;
|
|
959
|
-
|
|
960
|
-
// Get table schema first
|
|
961
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
962
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
963
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
964
|
-
);
|
|
965
|
-
|
|
966
|
-
if (!tableInfo) {
|
|
967
|
-
responseText = `Table "${table}" not found.`;
|
|
968
|
-
} else {
|
|
969
|
-
responseText = `Table Details: ${tableInfo.name}\n`;
|
|
970
|
-
responseText += `ID: ${tableInfo.id}\n`;
|
|
971
|
-
if (tableInfo.description) {
|
|
972
|
-
responseText += `Description: ${tableInfo.description}\n`;
|
|
973
|
-
}
|
|
974
|
-
responseText += `\nFields (${tableInfo.fields.length}):\n`;
|
|
975
|
-
|
|
976
|
-
tableInfo.fields.forEach((field, index) => {
|
|
977
|
-
responseText += `${index + 1}. ${field.name}\n`;
|
|
978
|
-
responseText += ` Type: ${field.type}\n`;
|
|
979
|
-
responseText += ` ID: ${field.id}\n`;
|
|
980
|
-
if (field.description) {
|
|
981
|
-
responseText += ` Description: ${field.description}\n`;
|
|
982
|
-
}
|
|
983
|
-
if (field.options) {
|
|
984
|
-
responseText += ` Options: ${JSON.stringify(field.options, null, 2)}\n`;
|
|
985
|
-
}
|
|
986
|
-
responseText += '\n';
|
|
987
|
-
});
|
|
988
|
-
|
|
989
|
-
if (tableInfo.views && tableInfo.views.length > 0) {
|
|
990
|
-
responseText += `Views (${tableInfo.views.length}):\n`;
|
|
991
|
-
tableInfo.views.forEach((view, index) => {
|
|
992
|
-
responseText += `${index + 1}. ${view.name} (${view.type})\n`;
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
else if (toolName === 'create_table') {
|
|
999
|
-
const { name, description, fields } = toolParams;
|
|
1000
|
-
|
|
1001
|
-
const body = {
|
|
1002
|
-
name,
|
|
1003
|
-
fields: fields.map(field => ({
|
|
1004
|
-
name: field.name,
|
|
1005
|
-
type: field.type,
|
|
1006
|
-
description: field.description,
|
|
1007
|
-
options: field.options
|
|
1008
|
-
}))
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
if (description) {
|
|
1012
|
-
body.description = description;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'POST', body);
|
|
1016
|
-
|
|
1017
|
-
responseText = `Successfully created table "${name}" (ID: ${result.id})\n`;
|
|
1018
|
-
responseText += `Fields created: ${result.fields.length}\n`;
|
|
1019
|
-
result.fields.forEach((field, index) => {
|
|
1020
|
-
responseText += `${index + 1}. ${field.name} (${field.type})\n`;
|
|
1021
|
-
});
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
else if (toolName === 'update_table') {
|
|
1025
|
-
const { table, name, description } = toolParams;
|
|
1026
|
-
|
|
1027
|
-
// Get table ID first
|
|
1028
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1029
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1030
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1031
|
-
);
|
|
1032
|
-
|
|
1033
|
-
if (!tableInfo) {
|
|
1034
|
-
responseText = `Table "${table}" not found.`;
|
|
1035
|
-
} else {
|
|
1036
|
-
const body = {};
|
|
1037
|
-
if (name) body.name = name;
|
|
1038
|
-
if (description !== undefined) body.description = description;
|
|
1039
|
-
|
|
1040
|
-
if (Object.keys(body).length === 0) {
|
|
1041
|
-
responseText = 'No updates specified. Provide name or description to update.';
|
|
1042
|
-
} else {
|
|
1043
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}`, 'PATCH', body);
|
|
1044
|
-
responseText = `Successfully updated table "${tableInfo.name}":\n`;
|
|
1045
|
-
if (name) responseText += `New name: ${result.name}\n`;
|
|
1046
|
-
if (description !== undefined) responseText += `New description: ${result.description || '(none)'}\n`;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
else if (toolName === 'delete_table') {
|
|
1052
|
-
const { table, confirm } = toolParams;
|
|
1053
|
-
|
|
1054
|
-
if (!confirm) {
|
|
1055
|
-
responseText = 'Table deletion requires confirm=true to proceed. This action cannot be undone!';
|
|
1056
|
-
} else {
|
|
1057
|
-
// Get table ID first
|
|
1058
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1059
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1060
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1061
|
-
);
|
|
1062
|
-
|
|
1063
|
-
if (!tableInfo) {
|
|
1064
|
-
responseText = `Table "${table}" not found.`;
|
|
1065
|
-
} else {
|
|
1066
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}`, 'DELETE');
|
|
1067
|
-
responseText = `Successfully deleted table "${tableInfo.name}" (ID: ${tableInfo.id})\n`;
|
|
1068
|
-
responseText += 'All data in this table has been permanently removed.';
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
// Field Management Tools
|
|
1074
|
-
else if (toolName === 'create_field') {
|
|
1075
|
-
const { table, name, type, description, options } = toolParams;
|
|
1076
|
-
|
|
1077
|
-
// Get table ID first
|
|
1078
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1079
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1080
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1081
|
-
);
|
|
1082
|
-
|
|
1083
|
-
if (!tableInfo) {
|
|
1084
|
-
responseText = `Table "${table}" not found.`;
|
|
1085
|
-
} else {
|
|
1086
|
-
const body = {
|
|
1087
|
-
name,
|
|
1088
|
-
type
|
|
1089
|
-
};
|
|
1090
|
-
|
|
1091
|
-
if (description) body.description = description;
|
|
1092
|
-
if (options) body.options = options;
|
|
1093
|
-
|
|
1094
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}/fields`, 'POST', body);
|
|
1095
|
-
|
|
1096
|
-
responseText = `Successfully created field "${name}" in table "${tableInfo.name}"\n`;
|
|
1097
|
-
responseText += `Field ID: ${result.id}\n`;
|
|
1098
|
-
responseText += `Type: ${result.type}\n`;
|
|
1099
|
-
if (result.description) {
|
|
1100
|
-
responseText += `Description: ${result.description}\n`;
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
else if (toolName === 'update_field') {
|
|
1106
|
-
const { table, fieldId, name, description, options } = toolParams;
|
|
1107
|
-
|
|
1108
|
-
// Get table ID first
|
|
1109
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1110
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1111
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1112
|
-
);
|
|
1113
|
-
|
|
1114
|
-
if (!tableInfo) {
|
|
1115
|
-
responseText = `Table "${table}" not found.`;
|
|
1116
|
-
} else {
|
|
1117
|
-
const body = {};
|
|
1118
|
-
if (name) body.name = name;
|
|
1119
|
-
if (description !== undefined) body.description = description;
|
|
1120
|
-
if (options) body.options = options;
|
|
1121
|
-
|
|
1122
|
-
if (Object.keys(body).length === 0) {
|
|
1123
|
-
responseText = 'No updates specified. Provide name, description, or options to update.';
|
|
1124
|
-
} else {
|
|
1125
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}/fields/${fieldId}`, 'PATCH', body);
|
|
1126
|
-
responseText = `Successfully updated field in table "${tableInfo.name}":\n`;
|
|
1127
|
-
responseText += `Field: ${result.name} (${result.type})\n`;
|
|
1128
|
-
responseText += `ID: ${result.id}\n`;
|
|
1129
|
-
if (result.description) {
|
|
1130
|
-
responseText += `Description: ${result.description}\n`;
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
else if (toolName === 'delete_field') {
|
|
1137
|
-
const { table, fieldId, confirm } = toolParams;
|
|
1138
|
-
|
|
1139
|
-
if (!confirm) {
|
|
1140
|
-
responseText = 'Field deletion requires confirm=true to proceed. This action cannot be undone!';
|
|
1141
|
-
} else {
|
|
1142
|
-
// Get table ID first
|
|
1143
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1144
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1145
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1146
|
-
);
|
|
1147
|
-
|
|
1148
|
-
if (!tableInfo) {
|
|
1149
|
-
responseText = `Table "${table}" not found.`;
|
|
1150
|
-
} else {
|
|
1151
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}/fields/${fieldId}`, 'DELETE');
|
|
1152
|
-
responseText = `Successfully deleted field from table "${tableInfo.name}"\n`;
|
|
1153
|
-
responseText += 'All data in this field has been permanently removed.';
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
else if (toolName === 'list_field_types') {
|
|
1159
|
-
responseText = `Available Airtable Field Types:\n\n`;
|
|
1160
|
-
responseText += `Basic Fields:\n`;
|
|
1161
|
-
responseText += `• singleLineText - Single line text input\n`;
|
|
1162
|
-
responseText += `• multilineText - Multi-line text input\n`;
|
|
1163
|
-
responseText += `• richText - Rich text with formatting\n`;
|
|
1164
|
-
responseText += `• number - Number field with optional formatting\n`;
|
|
1165
|
-
responseText += `• percent - Percentage field\n`;
|
|
1166
|
-
responseText += `• currency - Currency field\n`;
|
|
1167
|
-
responseText += `• singleSelect - Single choice from predefined options\n`;
|
|
1168
|
-
responseText += `• multipleSelectionList - Multiple choices from predefined options\n`;
|
|
1169
|
-
responseText += `• date - Date field\n`;
|
|
1170
|
-
responseText += `• dateTime - Date and time field\n`;
|
|
1171
|
-
responseText += `• phoneNumber - Phone number field\n`;
|
|
1172
|
-
responseText += `• email - Email address field\n`;
|
|
1173
|
-
responseText += `• url - URL field\n`;
|
|
1174
|
-
responseText += `• checkbox - Checkbox (true/false)\n`;
|
|
1175
|
-
responseText += `• rating - Star rating field\n`;
|
|
1176
|
-
responseText += `• duration - Duration/time field\n\n`;
|
|
1177
|
-
responseText += `Advanced Fields:\n`;
|
|
1178
|
-
responseText += `• multipleAttachment - File attachments\n`;
|
|
1179
|
-
responseText += `• linkedRecord - Link to records in another table\n`;
|
|
1180
|
-
responseText += `• lookup - Lookup values from linked records\n`;
|
|
1181
|
-
responseText += `• rollup - Calculate values from linked records\n`;
|
|
1182
|
-
responseText += `• count - Count of linked records\n`;
|
|
1183
|
-
responseText += `• formula - Calculated field with formulas\n`;
|
|
1184
|
-
responseText += `• createdTime - Auto-timestamp when record created\n`;
|
|
1185
|
-
responseText += `• createdBy - Auto-user who created record\n`;
|
|
1186
|
-
responseText += `• lastModifiedTime - Auto-timestamp when record modified\n`;
|
|
1187
|
-
responseText += `• lastModifiedBy - Auto-user who last modified record\n`;
|
|
1188
|
-
responseText += `• autoNumber - Auto-incrementing number\n`;
|
|
1189
|
-
responseText += `• barcode - Barcode/QR code field\n`;
|
|
1190
|
-
responseText += `• button - Action button field\n`;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
else if (toolName === 'get_table_views') {
|
|
1194
|
-
const { table } = toolParams;
|
|
1195
|
-
|
|
1196
|
-
// Get table schema
|
|
1197
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1198
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1199
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1200
|
-
);
|
|
1201
|
-
|
|
1202
|
-
if (!tableInfo) {
|
|
1203
|
-
responseText = `Table "${table}" not found.`;
|
|
1204
|
-
} else {
|
|
1205
|
-
if (tableInfo.views && tableInfo.views.length > 0) {
|
|
1206
|
-
responseText = `Views for table "${tableInfo.name}" (${tableInfo.views.length}):\n\n`;
|
|
1207
|
-
tableInfo.views.forEach((view, index) => {
|
|
1208
|
-
responseText += `${index + 1}. ${view.name}\n`;
|
|
1209
|
-
responseText += ` Type: ${view.type}\n`;
|
|
1210
|
-
responseText += ` ID: ${view.id}\n`;
|
|
1211
|
-
if (view.visibleFieldIds && view.visibleFieldIds.length > 0) {
|
|
1212
|
-
responseText += ` Visible fields: ${view.visibleFieldIds.length}\n`;
|
|
1213
|
-
}
|
|
1214
|
-
responseText += '\n';
|
|
1215
|
-
});
|
|
1216
|
-
} else {
|
|
1217
|
-
responseText = `No views found for table "${tableInfo.name}".`;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// NEW v1.6.0 TOOLS - Attachment and Batch Operations
|
|
1223
|
-
else if (toolName === 'upload_attachment') {
|
|
1224
|
-
const { table, recordId, fieldName, url, filename } = toolParams;
|
|
1225
|
-
|
|
1226
|
-
const attachment = { url };
|
|
1227
|
-
if (filename) attachment.filename = filename;
|
|
1228
|
-
|
|
1229
|
-
const updateBody = {
|
|
1230
|
-
fields: {
|
|
1231
|
-
[fieldName]: [attachment]
|
|
1232
|
-
}
|
|
1233
|
-
};
|
|
1234
|
-
|
|
1235
|
-
result = await callAirtableAPI(`${table}/${recordId}`, 'PATCH', updateBody);
|
|
1236
|
-
|
|
1237
|
-
responseText = `Successfully attached file to record ${recordId}:\n`;
|
|
1238
|
-
responseText += `Field: ${fieldName}\n`;
|
|
1239
|
-
responseText += `URL: ${url}\n`;
|
|
1240
|
-
if (filename) responseText += `Filename: ${filename}\n`;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
else if (toolName === 'batch_create_records') {
|
|
1244
|
-
const { table, records } = toolParams;
|
|
1245
|
-
|
|
1246
|
-
if (records.length > 10) {
|
|
1247
|
-
responseText = 'Error: Cannot create more than 10 records at once. Please split into smaller batches.';
|
|
1248
|
-
} else {
|
|
1249
|
-
const body = { records };
|
|
1250
|
-
result = await callAirtableAPI(table, 'POST', body);
|
|
1251
|
-
|
|
1252
|
-
responseText = `Successfully created ${result.records.length} records:\n`;
|
|
1253
|
-
result.records.forEach((record, index) => {
|
|
1254
|
-
responseText += `${index + 1}. ID: ${record.id}\n`;
|
|
1255
|
-
const fields = Object.keys(record.fields);
|
|
1256
|
-
if (fields.length > 0) {
|
|
1257
|
-
responseText += ` Fields: ${fields.join(', ')}\n`;
|
|
1258
|
-
}
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
else if (toolName === 'batch_update_records') {
|
|
1264
|
-
const { table, records } = toolParams;
|
|
1265
|
-
|
|
1266
|
-
if (records.length > 10) {
|
|
1267
|
-
responseText = 'Error: Cannot update more than 10 records at once. Please split into smaller batches.';
|
|
1268
|
-
} else {
|
|
1269
|
-
const body = { records };
|
|
1270
|
-
result = await callAirtableAPI(table, 'PATCH', body);
|
|
1271
|
-
|
|
1272
|
-
responseText = `Successfully updated ${result.records.length} records:\n`;
|
|
1273
|
-
result.records.forEach((record, index) => {
|
|
1274
|
-
responseText += `${index + 1}. ID: ${record.id}\n`;
|
|
1275
|
-
const fields = Object.keys(record.fields);
|
|
1276
|
-
if (fields.length > 0) {
|
|
1277
|
-
responseText += ` Updated fields: ${fields.join(', ')}\n`;
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
else if (toolName === 'batch_delete_records') {
|
|
1284
|
-
const { table, recordIds } = toolParams;
|
|
1285
|
-
|
|
1286
|
-
if (recordIds.length > 10) {
|
|
1287
|
-
responseText = 'Error: Cannot delete more than 10 records at once. Please split into smaller batches.';
|
|
1288
|
-
} else {
|
|
1289
|
-
const queryParams = { records: recordIds };
|
|
1290
|
-
result = await callAirtableAPI(table, 'DELETE', null, queryParams);
|
|
1291
|
-
|
|
1292
|
-
responseText = `Successfully deleted ${result.records.length} records:\n`;
|
|
1293
|
-
result.records.forEach((record, index) => {
|
|
1294
|
-
responseText += `${index + 1}. Deleted ID: ${record.id}\n`;
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
else if (toolName === 'batch_upsert_records') {
|
|
1300
|
-
const { table, records, keyFields } = toolParams;
|
|
1301
|
-
|
|
1302
|
-
if (records.length > 10) {
|
|
1303
|
-
responseText = 'Error: Cannot upsert more than 10 records at once. Please split into smaller batches.';
|
|
1304
|
-
} else {
|
|
1305
|
-
// For simplicity, we'll implement this as a batch create with merge fields
|
|
1306
|
-
// Note: Real upsert requires checking existing records first
|
|
1307
|
-
const body = {
|
|
1308
|
-
records,
|
|
1309
|
-
performUpsert: {
|
|
1310
|
-
fieldsToMergeOn: keyFields
|
|
1311
|
-
}
|
|
1312
|
-
};
|
|
1313
|
-
|
|
1314
|
-
result = await callAirtableAPI(table, 'PATCH', body);
|
|
1315
|
-
|
|
1316
|
-
responseText = `Successfully upserted ${result.records.length} records:\n`;
|
|
1317
|
-
result.records.forEach((record, index) => {
|
|
1318
|
-
responseText += `${index + 1}. ID: ${record.id}\n`;
|
|
1319
|
-
const fields = Object.keys(record.fields);
|
|
1320
|
-
if (fields.length > 0) {
|
|
1321
|
-
responseText += ` Fields: ${fields.join(', ')}\n`;
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// NEW v1.6.0 TOOLS - Advanced View Management
|
|
1328
|
-
else if (toolName === 'create_view') {
|
|
1329
|
-
const { table, name, type, visibleFieldIds, fieldOrder } = toolParams;
|
|
1330
|
-
|
|
1331
|
-
// Get table ID first
|
|
1332
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1333
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1334
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1335
|
-
);
|
|
1336
|
-
|
|
1337
|
-
if (!tableInfo) {
|
|
1338
|
-
responseText = `Table "${table}" not found.`;
|
|
1339
|
-
} else {
|
|
1340
|
-
const body = {
|
|
1341
|
-
name,
|
|
1342
|
-
type
|
|
1343
|
-
};
|
|
1344
|
-
|
|
1345
|
-
if (visibleFieldIds) body.visibleFieldIds = visibleFieldIds;
|
|
1346
|
-
if (fieldOrder) body.fieldOrder = fieldOrder;
|
|
1347
|
-
|
|
1348
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}/views`, 'POST', body);
|
|
1349
|
-
|
|
1350
|
-
responseText = `Successfully created view "${name}" in table "${tableInfo.name}":\n`;
|
|
1351
|
-
responseText += `View ID: ${result.id}\n`;
|
|
1352
|
-
responseText += `Type: ${result.type}\n`;
|
|
1353
|
-
if (result.visibleFieldIds && result.visibleFieldIds.length > 0) {
|
|
1354
|
-
responseText += `Visible fields: ${result.visibleFieldIds.length}\n`;
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
else if (toolName === 'get_view_metadata') {
|
|
1360
|
-
const { table, viewId } = toolParams;
|
|
1361
|
-
|
|
1362
|
-
// Get table ID first
|
|
1363
|
-
const schemaResult = await callAirtableAPI(`meta/bases/${baseId}/tables`, 'GET');
|
|
1364
|
-
const tableInfo = schemaResult.tables.find(t =>
|
|
1365
|
-
t.name.toLowerCase() === table.toLowerCase() || t.id === table
|
|
1366
|
-
);
|
|
1367
|
-
|
|
1368
|
-
if (!tableInfo) {
|
|
1369
|
-
responseText = `Table "${table}" not found.`;
|
|
1370
|
-
} else {
|
|
1371
|
-
result = await callAirtableAPI(`meta/bases/${baseId}/tables/${tableInfo.id}/views/${viewId}`, 'GET');
|
|
1372
|
-
|
|
1373
|
-
responseText = `View Metadata: ${result.name}\n`;
|
|
1374
|
-
responseText += `ID: ${result.id}\n`;
|
|
1375
|
-
responseText += `Type: ${result.type}\n`;
|
|
1376
|
-
|
|
1377
|
-
if (result.visibleFieldIds && result.visibleFieldIds.length > 0) {
|
|
1378
|
-
responseText += `\nVisible Fields (${result.visibleFieldIds.length}):\n`;
|
|
1379
|
-
result.visibleFieldIds.forEach((fieldId, index) => {
|
|
1380
|
-
responseText += `${index + 1}. ${fieldId}\n`;
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
|
-
if (result.filterByFormula) {
|
|
1385
|
-
responseText += `\nFilter Formula: ${result.filterByFormula}\n`;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
if (result.sorts && result.sorts.length > 0) {
|
|
1389
|
-
responseText += `\nSort Configuration:\n`;
|
|
1390
|
-
result.sorts.forEach((sort, index) => {
|
|
1391
|
-
responseText += `${index + 1}. Field: ${sort.field}, Direction: ${sort.direction}\n`;
|
|
1392
|
-
});
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
// NEW v1.6.0 TOOLS - Base Management
|
|
1398
|
-
else if (toolName === 'create_base') {
|
|
1399
|
-
const { name, workspaceId, tables } = toolParams;
|
|
1400
|
-
|
|
1401
|
-
const body = {
|
|
1402
|
-
name,
|
|
1403
|
-
tables: tables.map(table => ({
|
|
1404
|
-
name: table.name,
|
|
1405
|
-
description: table.description,
|
|
1406
|
-
fields: table.fields
|
|
1407
|
-
}))
|
|
1408
|
-
};
|
|
1409
|
-
|
|
1410
|
-
if (workspaceId) {
|
|
1411
|
-
body.workspaceId = workspaceId;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
result = await callAirtableAPI('meta/bases', 'POST', body);
|
|
1415
|
-
|
|
1416
|
-
responseText = `Successfully created base "${name}":\n`;
|
|
1417
|
-
responseText += `Base ID: ${result.id}\n`;
|
|
1418
|
-
if (result.tables && result.tables.length > 0) {
|
|
1419
|
-
responseText += `\nTables created (${result.tables.length}):\n`;
|
|
1420
|
-
result.tables.forEach((table, index) => {
|
|
1421
|
-
responseText += `${index + 1}. ${table.name} (ID: ${table.id})\n`;
|
|
1422
|
-
if (table.fields && table.fields.length > 0) {
|
|
1423
|
-
responseText += ` Fields: ${table.fields.length}\n`;
|
|
1424
|
-
}
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
else if (toolName === 'list_collaborators') {
|
|
1430
|
-
const { baseId: targetBaseId } = toolParams;
|
|
1431
|
-
const targetId = targetBaseId || baseId;
|
|
1432
|
-
|
|
1433
|
-
result = await callAirtableAPI(`meta/bases/${targetId}/collaborators`, 'GET');
|
|
1434
|
-
|
|
1435
|
-
if (result.collaborators && result.collaborators.length > 0) {
|
|
1436
|
-
responseText = `Base collaborators (${result.collaborators.length}):\n\n`;
|
|
1437
|
-
result.collaborators.forEach((collaborator, index) => {
|
|
1438
|
-
responseText += `${index + 1}. ${collaborator.email || collaborator.name || 'Unknown'}\n`;
|
|
1439
|
-
responseText += ` Permission: ${collaborator.permissionLevel || 'Unknown'}\n`;
|
|
1440
|
-
responseText += ` Type: ${collaborator.type || 'User'}\n`;
|
|
1441
|
-
if (collaborator.userId) {
|
|
1442
|
-
responseText += ` User ID: ${collaborator.userId}\n`;
|
|
1443
|
-
}
|
|
1444
|
-
responseText += '\n';
|
|
1445
|
-
});
|
|
1446
|
-
} else {
|
|
1447
|
-
responseText = 'No collaborators found for this base.';
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
else if (toolName === 'list_shares') {
|
|
1452
|
-
const { baseId: targetBaseId } = toolParams;
|
|
1453
|
-
const targetId = targetBaseId || baseId;
|
|
1454
|
-
|
|
1455
|
-
result = await callAirtableAPI(`meta/bases/${targetId}/shares`, 'GET');
|
|
1456
|
-
|
|
1457
|
-
if (result.shares && result.shares.length > 0) {
|
|
1458
|
-
responseText = `Shared views (${result.shares.length}):\n\n`;
|
|
1459
|
-
result.shares.forEach((share, index) => {
|
|
1460
|
-
responseText += `${index + 1}. ${share.name || 'Unnamed Share'}\n`;
|
|
1461
|
-
responseText += ` Share ID: ${share.id}\n`;
|
|
1462
|
-
responseText += ` URL: ${share.url}\n`;
|
|
1463
|
-
responseText += ` Type: ${share.type || 'View'}\n`;
|
|
1464
|
-
if (share.viewId) {
|
|
1465
|
-
responseText += ` View ID: ${share.viewId}\n`;
|
|
1466
|
-
}
|
|
1467
|
-
if (share.tableId) {
|
|
1468
|
-
responseText += ` Table ID: ${share.tableId}\n`;
|
|
1469
|
-
}
|
|
1470
|
-
responseText += ` Effective: ${share.effective ? 'Yes' : 'No'}\n`;
|
|
1471
|
-
responseText += '\n';
|
|
1472
|
-
});
|
|
1473
|
-
} else {
|
|
1474
|
-
responseText = 'No shared views found for this base.';
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
else {
|
|
1479
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
const response = {
|
|
1483
|
-
jsonrpc: '2.0',
|
|
1484
|
-
id: request.id,
|
|
1485
|
-
result: {
|
|
1486
|
-
content: [
|
|
1487
|
-
{
|
|
1488
|
-
type: 'text',
|
|
1489
|
-
text: responseText
|
|
1490
|
-
}
|
|
1491
|
-
]
|
|
1492
|
-
}
|
|
1493
|
-
};
|
|
1494
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1495
|
-
res.end(JSON.stringify(response));
|
|
1496
|
-
|
|
1497
|
-
} catch (error) {
|
|
1498
|
-
log(LOG_LEVELS.ERROR, `Tool ${toolName} error:`, error.message);
|
|
1499
|
-
|
|
1500
|
-
const response = {
|
|
1501
|
-
jsonrpc: '2.0',
|
|
1502
|
-
id: request.id,
|
|
1503
|
-
result: {
|
|
1504
|
-
content: [
|
|
1505
|
-
{
|
|
1506
|
-
type: 'text',
|
|
1507
|
-
text: `Error executing ${toolName}: ${error.message}`
|
|
1508
|
-
}
|
|
1509
|
-
]
|
|
1510
|
-
}
|
|
1511
|
-
};
|
|
1512
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1513
|
-
res.end(JSON.stringify(response));
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// Method not found
|
|
1520
|
-
const response = {
|
|
1521
|
-
jsonrpc: '2.0',
|
|
1522
|
-
id: request.id,
|
|
1523
|
-
error: {
|
|
1524
|
-
code: -32601,
|
|
1525
|
-
message: `Method ${request.method} not found`
|
|
1526
|
-
}
|
|
1527
|
-
};
|
|
1528
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1529
|
-
res.end(JSON.stringify(response));
|
|
1530
|
-
|
|
1531
|
-
} catch (error) {
|
|
1532
|
-
log(LOG_LEVELS.ERROR, 'Error processing request:', error);
|
|
1533
|
-
const response = {
|
|
1534
|
-
jsonrpc: '2.0',
|
|
1535
|
-
id: request.id || null,
|
|
1536
|
-
error: {
|
|
1537
|
-
code: -32000,
|
|
1538
|
-
message: error.message || 'Unknown error'
|
|
1539
|
-
}
|
|
1540
|
-
};
|
|
1541
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1542
|
-
res.end(JSON.stringify(response));
|
|
1543
|
-
}
|
|
1544
|
-
});
|
|
1545
|
-
});
|
|
1546
|
-
|
|
1547
|
-
// Start server
|
|
1548
|
-
const PORT = process.env.PORT || 8010;
|
|
1549
|
-
server.listen(PORT, () => {
|
|
1550
|
-
log(LOG_LEVELS.INFO, `Enhanced Airtable MCP server v1.4.0 running at http://localhost:${PORT}/mcp`);
|
|
1551
|
-
console.log(`For Claude, use this URL: http://localhost:${PORT}/mcp`);
|
|
1552
|
-
});
|
|
1553
|
-
|
|
1554
|
-
// Graceful shutdown
|
|
1555
|
-
process.on('SIGINT', () => {
|
|
1556
|
-
log(LOG_LEVELS.INFO, 'Shutting down server...');
|
|
1557
|
-
server.close(() => {
|
|
1558
|
-
log(LOG_LEVELS.INFO, 'Server stopped');
|
|
1559
|
-
process.exit(0);
|
|
1560
|
-
});
|
|
1561
|
-
});
|