@rashidazarang/airtable-mcp 1.6.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/.github/ISSUE_TEMPLATE/bug-report.yml +173 -0
  2. package/.github/ISSUE_TEMPLATE/feature-request.yml +209 -0
  3. package/.github/ISSUE_TEMPLATE/security-report.yml +216 -0
  4. package/.github/pull_request_template.md +245 -0
  5. package/.github/workflows/ci-cd.yml +408 -0
  6. package/.github/workflows/security-audit.yml +316 -0
  7. package/API_DOCUMENTATION.md +897 -0
  8. package/CODE_OF_CONDUCT.md +181 -0
  9. package/Dockerfile.production +127 -0
  10. package/README.md +1 -0
  11. package/airtable-clipper/CHANGELOG.md +198 -0
  12. package/airtable-clipper/CHROME_STORE_SUBMISSION.md +343 -0
  13. package/airtable-clipper/LAUNCH_STRATEGY.md +495 -0
  14. package/airtable-clipper/LICENSE +21 -0
  15. package/airtable-clipper/OAUTH_SETUP.md +51 -0
  16. package/airtable-clipper/PRIVACY_POLICY.md +187 -0
  17. package/airtable-clipper/README.md +575 -0
  18. package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +273 -0
  19. package/airtable-clipper/build.sh +85 -0
  20. package/airtable-clipper/docs/QUICK_START.md +99 -0
  21. package/airtable-clipper/docs/SETUP.md +291 -0
  22. package/airtable-clipper/extension/background.js +337 -0
  23. package/airtable-clipper/extension/base-setup.html +324 -0
  24. package/airtable-clipper/extension/base-setup.js +471 -0
  25. package/airtable-clipper/extension/content.js +771 -0
  26. package/airtable-clipper/extension/icons/README.md +69 -0
  27. package/airtable-clipper/extension/icons/icon-16.png +3 -0
  28. package/airtable-clipper/extension/manifest.json +73 -0
  29. package/airtable-clipper/extension/popup.html +144 -0
  30. package/airtable-clipper/extension/popup.js +475 -0
  31. package/airtable-clipper/extension/styles/content.css +229 -0
  32. package/airtable-clipper/extension/styles/popup.css +477 -0
  33. package/airtable-clipper/privacy-policy.md +63 -0
  34. package/airtable-clipper/releases/v1.0.0/background.js +337 -0
  35. package/airtable-clipper/releases/v1.0.0/base-setup.html +324 -0
  36. package/airtable-clipper/releases/v1.0.0/base-setup.js +471 -0
  37. package/airtable-clipper/releases/v1.0.0/content.js +771 -0
  38. package/airtable-clipper/releases/v1.0.0/icons/README.md +69 -0
  39. package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +2 -0
  40. package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +3 -0
  41. package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +2 -0
  42. package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +2 -0
  43. package/airtable-clipper/releases/v1.0.0/manifest.json +73 -0
  44. package/airtable-clipper/releases/v1.0.0/popup.html +144 -0
  45. package/airtable-clipper/releases/v1.0.0/popup.js +475 -0
  46. package/airtable-clipper/releases/v1.0.0/sidepanel.html +25 -0
  47. package/airtable-clipper/releases/v1.0.0/styles/content.css +229 -0
  48. package/airtable-clipper/releases/v1.0.0/styles/popup.css +477 -0
  49. package/airtable-clipper/releases/v1.0.1/background.js +337 -0
  50. package/airtable-clipper/releases/v1.0.1/base-setup.html +324 -0
  51. package/airtable-clipper/releases/v1.0.1/base-setup.js +471 -0
  52. package/airtable-clipper/releases/v1.0.1/content.js +771 -0
  53. package/airtable-clipper/releases/v1.0.1/icons/README.md +69 -0
  54. package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +2 -0
  55. package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +3 -0
  56. package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +2 -0
  57. package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +2 -0
  58. package/airtable-clipper/releases/v1.0.1/manifest.json +70 -0
  59. package/airtable-clipper/releases/v1.0.1/popup.html +157 -0
  60. package/airtable-clipper/releases/v1.0.1/popup.js +562 -0
  61. package/airtable-clipper/releases/v1.0.1/sidepanel.html +25 -0
  62. package/airtable-clipper/releases/v1.0.1/styles/content.css +229 -0
  63. package/airtable-clipper/releases/v1.0.1/styles/popup.css +647 -0
  64. package/airtable-clipper/releases/v1.0.2/background.js +337 -0
  65. package/airtable-clipper/releases/v1.0.2/base-setup.html +324 -0
  66. package/airtable-clipper/releases/v1.0.2/base-setup.js +471 -0
  67. package/airtable-clipper/releases/v1.0.2/content.js +771 -0
  68. package/airtable-clipper/releases/v1.0.2/icons/README.md +69 -0
  69. package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +2 -0
  70. package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +3 -0
  71. package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +2 -0
  72. package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +2 -0
  73. package/airtable-clipper/releases/v1.0.2/manifest.json +62 -0
  74. package/airtable-clipper/releases/v1.0.2/popup.html +157 -0
  75. package/airtable-clipper/releases/v1.0.2/popup.js +567 -0
  76. package/airtable-clipper/releases/v1.0.2/sidepanel.html +25 -0
  77. package/airtable-clipper/releases/v1.0.2/styles/content.css +229 -0
  78. package/airtable-clipper/releases/v1.0.2/styles/popup.css +647 -0
  79. package/airtable-clipper/terms-of-service.md +124 -0
  80. package/airtable-clipper/test-credentials.md +61 -0
  81. package/airtable-clipper/test-extension/background.js +337 -0
  82. package/airtable-clipper/test-extension/base-setup.html +324 -0
  83. package/airtable-clipper/test-extension/base-setup.js +471 -0
  84. package/airtable-clipper/test-extension/content.js +873 -0
  85. package/airtable-clipper/test-extension/icons/README.md +69 -0
  86. package/airtable-clipper/test-extension/icons/icon-128.png +2 -0
  87. package/airtable-clipper/test-extension/icons/icon-16.png +3 -0
  88. package/airtable-clipper/test-extension/icons/icon-32.png +2 -0
  89. package/airtable-clipper/test-extension/icons/icon-48.png +2 -0
  90. package/airtable-clipper/test-extension/manifest.json +72 -0
  91. package/airtable-clipper/test-extension/popup.html +274 -0
  92. package/airtable-clipper/test-extension/popup.js +729 -0
  93. package/airtable-clipper/test-extension/sidepanel.html +25 -0
  94. package/airtable-clipper/test-extension/styles/content.css +229 -0
  95. package/airtable-clipper/test-extension/styles/popup.css +794 -0
  96. package/airtable_mcp_v2.js +1505 -0
  97. package/airtable_mcp_v2_oauth.js +1048 -0
  98. package/airtable_mcp_v3_advanced.js +1161 -0
  99. package/airtable_simple_production.js +532 -0
  100. package/docker-compose.production.yml +366 -0
  101. package/helm/airtable-mcp/Chart.yaml +122 -0
  102. package/helm/airtable-mcp/values.yaml +538 -0
  103. package/k8s/deployment.yaml +402 -0
  104. package/k8s/namespace.yaml +108 -0
  105. package/k8s/service.yaml +194 -0
  106. package/monitoring/alerts.yml +289 -0
  107. package/monitoring/prometheus.yml +224 -0
  108. package/package.json +6 -6
  109. package/.claude/settings.local.json +0 -12
  110. package/airtable-mcp-1.1.0.tgz +0 -0
  111. package/airtable_enhanced.js +0 -499
  112. package/airtable_simple_v1.2.4_backup.js +0 -277
  113. package/airtable_v1.4.0.js +0 -654
  114. package/rashidazarang-airtable-mcp-1.1.0.tgz +0 -0
  115. package/rashidazarang-airtable-mcp-1.2.0.tgz +0 -0
  116. package/rashidazarang-airtable-mcp-1.2.1.tgz +0 -0
@@ -1,499 +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.3.0`);
59
- log(LOG_LEVELS.INFO, `Token: ${token.slice(0, 5)}...${token.slice(-5)}`);
60
- log(LOG_LEVELS.INFO, `Base ID: ${baseId}`);
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/');
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
- }
254
- };
255
- res.writeHead(200, { 'Content-Type': 'application/json' });
256
- res.end(JSON.stringify(response));
257
- return;
258
- }
259
-
260
- if (request.method === 'resources/list') {
261
- const response = {
262
- jsonrpc: '2.0',
263
- id: request.id,
264
- result: {
265
- resources: [
266
- {
267
- id: 'airtable_tables',
268
- name: 'Airtable Tables',
269
- description: 'Tables in your Airtable base'
270
- }
271
- ]
272
- }
273
- };
274
- res.writeHead(200, { 'Content-Type': 'application/json' });
275
- res.end(JSON.stringify(response));
276
- return;
277
- }
278
-
279
- if (request.method === 'prompts/list') {
280
- const response = {
281
- jsonrpc: '2.0',
282
- id: request.id,
283
- result: {
284
- prompts: [
285
- {
286
- id: 'tables_prompt',
287
- name: 'List Tables',
288
- description: 'List all tables'
289
- }
290
- ]
291
- }
292
- };
293
- res.writeHead(200, { 'Content-Type': 'application/json' });
294
- res.end(JSON.stringify(response));
295
- return;
296
- }
297
-
298
- // Handle tool calls
299
- if (request.method === 'tools/call') {
300
- const toolName = request.params.name;
301
- const toolParams = request.params.arguments || {};
302
-
303
- let result;
304
- let responseText;
305
-
306
- try {
307
- // LIST TABLES
308
- if (toolName === 'list_tables') {
309
- result = await callAirtableAPI(`meta/bases/${baseId}/tables`);
310
- const tables = result.tables || [];
311
-
312
- responseText = tables.length > 0
313
- ? `Found ${tables.length} table(s):\n` + tables.map((table, i) =>
314
- `${i+1}. ${table.name} (ID: ${table.id}, Fields: ${table.fields?.length || 0})`
315
- ).join('\n')
316
- : 'No tables found in this base.';
317
- }
318
-
319
- // LIST RECORDS
320
- else if (toolName === 'list_records') {
321
- const { table, maxRecords, view } = toolParams;
322
-
323
- const queryParams = {};
324
- if (maxRecords) queryParams.maxRecords = maxRecords;
325
- if (view) queryParams.view = view;
326
-
327
- result = await callAirtableAPI(`${table}`, 'GET', null, queryParams);
328
- const records = result.records || [];
329
-
330
- responseText = records.length > 0
331
- ? `Found ${records.length} record(s) in table "${table}":\n` +
332
- records.map((record, i) =>
333
- `${i+1}. ID: ${record.id}\n Fields: ${JSON.stringify(record.fields, null, 2)}`
334
- ).join('\n\n')
335
- : `No records found in table "${table}".`;
336
- }
337
-
338
- // GET SINGLE RECORD
339
- else if (toolName === 'get_record') {
340
- const { table, recordId } = toolParams;
341
-
342
- result = await callAirtableAPI(`${table}/${recordId}`);
343
-
344
- responseText = `Record ${recordId} from table "${table}":\n` +
345
- JSON.stringify(result.fields, null, 2) +
346
- `\n\nCreated: ${result.createdTime}`;
347
- }
348
-
349
- // CREATE RECORD
350
- else if (toolName === 'create_record') {
351
- const { table, fields } = toolParams;
352
-
353
- const body = {
354
- fields: fields
355
- };
356
-
357
- result = await callAirtableAPI(table, 'POST', body);
358
-
359
- responseText = `Successfully created record in table "${table}":\n` +
360
- `Record ID: ${result.id}\n` +
361
- `Fields: ${JSON.stringify(result.fields, null, 2)}\n` +
362
- `Created at: ${result.createdTime}`;
363
- }
364
-
365
- // UPDATE RECORD
366
- else if (toolName === 'update_record') {
367
- const { table, recordId, fields } = toolParams;
368
-
369
- const body = {
370
- fields: fields
371
- };
372
-
373
- result = await callAirtableAPI(`${table}/${recordId}`, 'PATCH', body);
374
-
375
- responseText = `Successfully updated record ${recordId} in table "${table}":\n` +
376
- `Updated fields: ${JSON.stringify(result.fields, null, 2)}`;
377
- }
378
-
379
- // DELETE RECORD
380
- else if (toolName === 'delete_record') {
381
- const { table, recordId } = toolParams;
382
-
383
- result = await callAirtableAPI(`${table}/${recordId}`, 'DELETE');
384
-
385
- responseText = `Successfully deleted record ${recordId} from table "${table}".\n` +
386
- `Deleted record ID: ${result.id}\n` +
387
- `Deleted: ${result.deleted}`;
388
- }
389
-
390
- // SEARCH RECORDS
391
- else if (toolName === 'search_records') {
392
- const { table, filterByFormula, sort, maxRecords, fields } = toolParams;
393
-
394
- const queryParams = {};
395
- if (filterByFormula) queryParams.filterByFormula = filterByFormula;
396
- if (maxRecords) queryParams.maxRecords = maxRecords;
397
- if (fields && fields.length > 0) queryParams.fields = fields;
398
- if (sort && sort.length > 0) {
399
- sort.forEach((s, i) => {
400
- queryParams[`sort[${i}][field]`] = s.field;
401
- queryParams[`sort[${i}][direction]`] = s.direction || 'asc';
402
- });
403
- }
404
-
405
- result = await callAirtableAPI(table, 'GET', null, queryParams);
406
- const records = result.records || [];
407
-
408
- responseText = records.length > 0
409
- ? `Found ${records.length} matching record(s) in table "${table}":\n` +
410
- records.map((record, i) =>
411
- `${i+1}. ID: ${record.id}\n Fields: ${JSON.stringify(record.fields, null, 2)}`
412
- ).join('\n\n')
413
- : `No records found matching the search criteria in table "${table}".`;
414
- }
415
-
416
- else {
417
- throw new Error(`Unknown tool: ${toolName}`);
418
- }
419
-
420
- const response = {
421
- jsonrpc: '2.0',
422
- id: request.id,
423
- result: {
424
- content: [
425
- {
426
- type: 'text',
427
- text: responseText
428
- }
429
- ]
430
- }
431
- };
432
- res.writeHead(200, { 'Content-Type': 'application/json' });
433
- res.end(JSON.stringify(response));
434
-
435
- } catch (error) {
436
- log(LOG_LEVELS.ERROR, `Tool ${toolName} error:`, error.message);
437
-
438
- const response = {
439
- jsonrpc: '2.0',
440
- id: request.id,
441
- result: {
442
- content: [
443
- {
444
- type: 'text',
445
- text: `Error executing ${toolName}: ${error.message}`
446
- }
447
- ]
448
- }
449
- };
450
- res.writeHead(200, { 'Content-Type': 'application/json' });
451
- res.end(JSON.stringify(response));
452
- }
453
-
454
- return;
455
- }
456
-
457
- // Method not found
458
- const response = {
459
- jsonrpc: '2.0',
460
- id: request.id,
461
- error: {
462
- code: -32601,
463
- message: `Method ${request.method} not found`
464
- }
465
- };
466
- res.writeHead(200, { 'Content-Type': 'application/json' });
467
- res.end(JSON.stringify(response));
468
-
469
- } catch (error) {
470
- log(LOG_LEVELS.ERROR, 'Error processing request:', error);
471
- const response = {
472
- jsonrpc: '2.0',
473
- id: request.id || null,
474
- error: {
475
- code: -32000,
476
- message: error.message || 'Unknown error'
477
- }
478
- };
479
- res.writeHead(200, { 'Content-Type': 'application/json' });
480
- res.end(JSON.stringify(response));
481
- }
482
- });
483
- });
484
-
485
- // Start server
486
- const PORT = process.env.PORT || 8010;
487
- server.listen(PORT, () => {
488
- log(LOG_LEVELS.INFO, `Enhanced Airtable MCP server v1.3.0 running at http://localhost:${PORT}/mcp`);
489
- console.log(`For Claude, use this URL: http://localhost:${PORT}/mcp`);
490
- });
491
-
492
- // Graceful shutdown
493
- process.on('SIGINT', () => {
494
- log(LOG_LEVELS.INFO, 'Shutting down server...');
495
- server.close(() => {
496
- log(LOG_LEVELS.INFO, 'Server stopped');
497
- process.exit(0);
498
- });
499
- });