#
tokens: 35904/50000 2/119 files (page 4/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 of 5. Use http://codebase.md/rashidazarang/airtable-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .eslintrc.js
├── .github
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── custom.md
│   │   └── feature_request.md
│   └── pull_request_template.md
├── .gitignore
├── .nvmrc
├── .prettierrc
├── bin
│   ├── airtable-crud-cli.js
│   └── airtable-mcp.js
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── docker
│   ├── Dockerfile
│   └── Dockerfile.node
├── docs
│   ├── guides
│   │   ├── CLAUDE_INTEGRATION.md
│   │   ├── ENHANCED_FEATURES.md
│   │   ├── INSTALLATION.md
│   │   └── QUICK_START.md
│   └── releases
│       ├── RELEASE_NOTES_v1.2.2.md
│       ├── RELEASE_NOTES_v1.2.4.md
│       ├── RELEASE_NOTES_v1.4.0.md
│       ├── RELEASE_NOTES_v1.5.0.md
│       └── RELEASE_NOTES_v1.6.0.md
├── examples
│   ├── airtable-crud-example.js
│   ├── building-mcp.md
│   ├── claude_config.json
│   ├── claude_simple_config.json
│   ├── env-demo.js
│   ├── example_usage.md
│   ├── example-tasks-update.json
│   ├── example-tasks.json
│   ├── python_debug_patch.txt
│   ├── sample-transform.js
│   ├── typescript
│   │   ├── advanced-ai-prompts.ts
│   │   ├── basic-usage.ts
│   │   └── claude-desktop-config.json
│   └── windsurf_mcp_config.json
├── index.js
├── ISSUE_RESPONSES.md
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── PROJECT_STRUCTURE.md
├── README.md
├── RELEASE_SUMMARY_v3.2.x.md
├── RELEASE_v3.2.1.md
├── RELEASE_v3.2.3.md
├── RELEASE_v3.2.4.md
├── requirements.txt
├── SECURITY_NOTICE.md
├── smithery.yaml
├── src
│   ├── index.js
│   ├── javascript
│   │   ├── airtable_simple_production.js
│   │   └── airtable_simple.js
│   ├── python
│   │   ├── airtable_mcp
│   │   │   ├── __init__.py
│   │   │   └── src
│   │   │       └── server.py
│   │   ├── inspector_server.py
│   │   ├── inspector.py
│   │   ├── setup.py
│   │   ├── simple_airtable_server.py
│   │   └── test_client.py
│   └── typescript
│       ├── ai-prompts.d.ts
│       ├── airtable-mcp-server.d.ts
│       ├── airtable-mcp-server.ts
│       ├── app
│       │   ├── airtable-client.ts
│       │   ├── config.ts
│       │   ├── context.ts
│       │   ├── exceptions.ts
│       │   ├── governance.ts
│       │   ├── logger.ts
│       │   ├── rateLimiter.ts
│       │   ├── tools
│       │   │   ├── create.ts
│       │   │   ├── describe.ts
│       │   │   ├── handleError.ts
│       │   │   ├── index.ts
│       │   │   ├── listBases.ts
│       │   │   ├── listExceptions.ts
│       │   │   ├── listGovernance.ts
│       │   │   ├── query.ts
│       │   │   ├── update.ts
│       │   │   ├── upsert.ts
│       │   │   └── webhooks.ts
│       │   └── types.ts
│       ├── errors.ts
│       ├── index.d.ts
│       ├── index.ts
│       ├── prompt-templates.ts
│       ├── tools-schemas.ts
│       └── tools.d.ts
├── TESTING_REPORT.md
├── tests
│   ├── test_all_features.sh
│   ├── test_mcp_comprehensive.js
│   ├── test_v1.5.0_final.sh
│   └── test_v1.6.0_comprehensive.sh
├── tsconfig.json
└── types
    └── typescript
        ├── airtable-mcp-server.d.ts
        ├── app
        │   ├── airtable-client.d.ts
        │   ├── config.d.ts
        │   ├── context.d.ts
        │   ├── exceptions.d.ts
        │   ├── governance.d.ts
        │   ├── logger.d.ts
        │   ├── rateLimiter.d.ts
        │   ├── tools
        │   │   ├── create.d.ts
        │   │   ├── describe.d.ts
        │   │   ├── handleError.d.ts
        │   │   ├── index.d.ts
        │   │   ├── listBases.d.ts
        │   │   ├── listExceptions.d.ts
        │   │   ├── listGovernance.d.ts
        │   │   ├── query.d.ts
        │   │   ├── update.d.ts
        │   │   ├── upsert.d.ts
        │   │   └── webhooks.d.ts
        │   └── types.d.ts
        ├── errors.d.ts
        ├── index.d.ts
        ├── prompt-templates.d.ts
        ├── test-suite.d.ts
        └── tools-schemas.d.ts
```

# Files

--------------------------------------------------------------------------------
/src/javascript/airtable_simple_production.js:
--------------------------------------------------------------------------------

```javascript
   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 | let 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 |     // Sanitize message to prevent format string attacks
  66 |     const safeMessage = String(message).replace(/%/g, '%%');
  67 |     const output = `[${timestamp}] [${levelName}] ${safeMessage}`;
  68 |     
  69 |     if (Object.keys(metadata).length > 0) {
  70 |       // Use separate arguments to avoid format string injection
  71 |       console.log('%s %s', output, JSON.stringify(metadata));
  72 |     } else {
  73 |       console.log('%s', output);
  74 |     }
  75 |   }
  76 | }
  77 | 
  78 | // Rate limiting
  79 | const rateLimiter = new Map();
  80 | 
  81 | function checkRateLimit(clientId) {
  82 |   const now = Date.now();
  83 |   const windowStart = now - 60000; // 1 minute window
  84 |   
  85 |   if (!rateLimiter.has(clientId)) {
  86 |     rateLimiter.set(clientId, []);
  87 |   }
  88 |   
  89 |   const requests = rateLimiter.get(clientId);
  90 |   const recentRequests = requests.filter(time => time > windowStart);
  91 |   
  92 |   if (recentRequests.length >= CONFIG.MAX_REQUESTS_PER_MINUTE) {
  93 |     return false;
  94 |   }
  95 |   
  96 |   recentRequests.push(now);
  97 |   rateLimiter.set(clientId, recentRequests);
  98 |   return true;
  99 | }
 100 | 
 101 | // Input validation and HTML escaping
 102 | function sanitizeInput(input) {
 103 |   if (typeof input === 'string') {
 104 |     return input.replace(/[<>]/g, '').trim().substring(0, 1000);
 105 |   }
 106 |   return input;
 107 | }
 108 | 
 109 | function escapeHtml(unsafe) {
 110 |   if (typeof unsafe !== 'string') {
 111 |     return String(unsafe);
 112 |   }
 113 |   return unsafe
 114 |     .replace(/&/g, "&amp;")
 115 |     .replace(/</g, "&lt;")
 116 |     .replace(/>/g, "&gt;")
 117 |     .replace(/"/g, "&quot;")
 118 |     .replace(/'/g, "&#039;")
 119 |     .replace(/\//g, "&#x2F;");
 120 | }
 121 | 
 122 | function validateUrl(url) {
 123 |   try {
 124 |     const parsed = new URL(url);
 125 |     // Only allow http and https protocols
 126 |     return ['http:', 'https:'].includes(parsed.protocol);
 127 |   } catch {
 128 |     return false;
 129 |   }
 130 | }
 131 | 
 132 | // Airtable API integration
 133 | function callAirtableAPI(endpoint, method = 'GET', body = null, queryParams = {}) {
 134 |   return new Promise((resolve, reject) => {
 135 |     const isBaseEndpoint = !endpoint.startsWith('meta/');
 136 |     const baseUrl = isBaseEndpoint ? `${baseId}/${endpoint}` : endpoint;
 137 |     
 138 |     const queryString = Object.keys(queryParams).length > 0 
 139 |       ? '?' + new URLSearchParams(queryParams).toString() 
 140 |       : '';
 141 |     
 142 |     const apiUrl = `https://api.airtable.com/v0/${baseUrl}${queryString}`;
 143 |     const urlObj = new URL(apiUrl);
 144 |     
 145 |     log(LOG_LEVELS.DEBUG, 'API Request', { method, url: apiUrl });
 146 |     
 147 |     const options = {
 148 |       hostname: urlObj.hostname,
 149 |       path: urlObj.pathname + urlObj.search,
 150 |       method: method,
 151 |       headers: {
 152 |         'Authorization': `Bearer ${token}`,
 153 |         'Content-Type': 'application/json',
 154 |         'User-Agent': 'Airtable-MCP-Server/2.1.0'
 155 |       }
 156 |     };
 157 |     
 158 |     const req = https.request(options, (response) => {
 159 |       let data = '';
 160 |       
 161 |       response.on('data', (chunk) => data += chunk);
 162 |       response.on('end', () => {
 163 |         try {
 164 |           const parsed = data ? JSON.parse(data) : {};
 165 |           
 166 |           if (response.statusCode >= 200 && response.statusCode < 300) {
 167 |             resolve(parsed);
 168 |           } else {
 169 |             const error = parsed.error || {};
 170 |             reject(new Error(`Airtable API error (${response.statusCode}): ${error.message || error.type || 'Unknown error'}`));
 171 |           }
 172 |         } catch (e) {
 173 |           reject(new Error(`Failed to parse Airtable response: ${e.message}`));
 174 |         }
 175 |       });
 176 |     });
 177 |     
 178 |     req.on('error', reject);
 179 |     
 180 |     if (body) {
 181 |       req.write(JSON.stringify(body));
 182 |     }
 183 |     
 184 |     req.end();
 185 |   });
 186 | }
 187 | 
 188 | // Tools schema
 189 | const TOOLS_SCHEMA = [
 190 |   {
 191 |     name: 'list_tables',
 192 |     description: 'List all tables in the Airtable base',
 193 |     inputSchema: {
 194 |       type: 'object',
 195 |       properties: {
 196 |         include_schema: { type: 'boolean', description: 'Include field schema information', default: false }
 197 |       }
 198 |     }
 199 |   },
 200 |   {
 201 |     name: 'list_records',
 202 |     description: 'List records from a specific table',
 203 |     inputSchema: {
 204 |       type: 'object',
 205 |       properties: {
 206 |         table: { type: 'string', description: 'Table name or ID' },
 207 |         maxRecords: { type: 'number', description: 'Maximum number of records to return' },
 208 |         view: { type: 'string', description: 'View name or ID' },
 209 |         filterByFormula: { type: 'string', description: 'Airtable formula to filter records' }
 210 |       },
 211 |       required: ['table']
 212 |     }
 213 |   },
 214 |   {
 215 |     name: 'get_record',
 216 |     description: 'Get a single record by ID',
 217 |     inputSchema: {
 218 |       type: 'object',
 219 |       properties: {
 220 |         table: { type: 'string', description: 'Table name or ID' },
 221 |         recordId: { type: 'string', description: 'Record ID' }
 222 |       },
 223 |       required: ['table', 'recordId']
 224 |     }
 225 |   },
 226 |   {
 227 |     name: 'create_record',
 228 |     description: 'Create a new record in a table',
 229 |     inputSchema: {
 230 |       type: 'object',
 231 |       properties: {
 232 |         table: { type: 'string', description: 'Table name or ID' },
 233 |         fields: { type: 'object', description: 'Field values for the new record' }
 234 |       },
 235 |       required: ['table', 'fields']
 236 |     }
 237 |   },
 238 |   {
 239 |     name: 'update_record',
 240 |     description: 'Update an existing record',
 241 |     inputSchema: {
 242 |       type: 'object',
 243 |       properties: {
 244 |         table: { type: 'string', description: 'Table name or ID' },
 245 |         recordId: { type: 'string', description: 'Record ID to update' },
 246 |         fields: { type: 'object', description: 'Fields to update' }
 247 |       },
 248 |       required: ['table', 'recordId', 'fields']
 249 |     }
 250 |   },
 251 |   {
 252 |     name: 'delete_record',
 253 |     description: 'Delete a record from a table',
 254 |     inputSchema: {
 255 |       type: 'object',
 256 |       properties: {
 257 |         table: { type: 'string', description: 'Table name or ID' },
 258 |         recordId: { type: 'string', description: 'Record ID to delete' }
 259 |       },
 260 |       required: ['table', 'recordId']
 261 |     }
 262 |   }
 263 | ];
 264 | 
 265 | // Enhanced AI-powered prompts for intelligent Airtable operations
 266 | const PROMPTS_SCHEMA = [
 267 |   {
 268 |     name: 'analyze_data',
 269 |     description: 'Advanced AI data analysis with statistical insights, pattern recognition, and predictive modeling',
 270 |     arguments: [
 271 |       {
 272 |         name: 'table',
 273 |         description: 'Table name or ID to analyze',
 274 |         required: true
 275 |       },
 276 |       {
 277 |         name: 'analysis_type',
 278 |         description: 'Type of analysis (trends, statistical, patterns, predictive, anomaly_detection, correlation_matrix)',
 279 |         required: false
 280 |       },
 281 |       {
 282 |         name: 'field_focus',
 283 |         description: 'Specific fields to focus the analysis on',
 284 |         required: false
 285 |       },
 286 |       {
 287 |         name: 'time_dimension',
 288 |         description: 'Time field for temporal analysis',
 289 |         required: false
 290 |       },
 291 |       {
 292 |         name: 'confidence_level',
 293 |         description: 'Statistical confidence level (0.90, 0.95, 0.99)',
 294 |         required: false
 295 |       }
 296 |     ]
 297 |   },
 298 |   {
 299 |     name: 'create_report',
 300 |     description: 'Generate intelligent reports with AI-powered insights, visualizations, and actionable recommendations',
 301 |     arguments: [
 302 |       {
 303 |         name: 'table',
 304 |         description: 'Table name or ID for the report',
 305 |         required: true
 306 |       },
 307 |       {
 308 |         name: 'report_type',
 309 |         description: 'Type of report (executive_summary, operational_dashboard, analytical_deep_dive, performance_metrics, predictive_forecast)',
 310 |         required: false
 311 |       },
 312 |       {
 313 |         name: 'time_period',
 314 |         description: 'Time period for analysis (last_7_days, last_30_days, last_quarter, year_to_date, custom)',
 315 |         required: false
 316 |       },
 317 |       {
 318 |         name: 'stakeholder_level',
 319 |         description: 'Target audience (executive, manager, analyst, operational)',
 320 |         required: false
 321 |       },
 322 |       {
 323 |         name: 'include_recommendations',
 324 |         description: 'Include AI-generated actionable recommendations (true/false)',
 325 |         required: false
 326 |       }
 327 |     ]
 328 |   },
 329 |   {
 330 |     name: 'data_insights',
 331 |     description: 'Discover hidden patterns, correlations, and business insights using advanced AI algorithms',
 332 |     arguments: [
 333 |       {
 334 |         name: 'tables',
 335 |         description: 'Comma-separated list of table names to analyze',
 336 |         required: true
 337 |       },
 338 |       {
 339 |         name: 'insight_type',
 340 |         description: 'Type of insights (correlations, outliers, trends, predictions, segmentation, attribution, churn_analysis)',
 341 |         required: false
 342 |       },
 343 |       {
 344 |         name: 'business_context',
 345 |         description: 'Business domain context (sales, marketing, operations, finance, customer_success)',
 346 |         required: false
 347 |       },
 348 |       {
 349 |         name: 'insight_depth',
 350 |         description: 'Analysis depth (surface, moderate, deep, comprehensive)',
 351 |         required: false
 352 |       }
 353 |     ]
 354 |   },
 355 |   {
 356 |     name: 'optimize_workflow',
 357 |     description: 'AI-powered workflow optimization with automation recommendations and efficiency improvements',
 358 |     arguments: [
 359 |       {
 360 |         name: 'base_overview',
 361 |         description: 'Overview of the base structure and current workflows',
 362 |         required: false
 363 |       },
 364 |       {
 365 |         name: 'optimization_focus',
 366 |         description: 'Focus area (automation, data_quality, collaboration, performance, integration, user_experience)',
 367 |         required: false
 368 |       },
 369 |       {
 370 |         name: 'current_pain_points',
 371 |         description: 'Known issues or bottlenecks in current workflow',
 372 |         required: false
 373 |       },
 374 |       {
 375 |         name: 'team_size',
 376 |         description: 'Number of users working with this base',
 377 |         required: false
 378 |       }
 379 |     ]
 380 |   },
 381 |   {
 382 |     name: 'smart_schema_design',
 383 |     description: 'AI-assisted database schema optimization and field relationship analysis',
 384 |     arguments: [
 385 |       {
 386 |         name: 'use_case',
 387 |         description: 'Primary use case (crm, project_management, inventory, content_management, hr, finance)',
 388 |         required: true
 389 |       },
 390 |       {
 391 |         name: 'data_volume',
 392 |         description: 'Expected data volume (small, medium, large, enterprise)',
 393 |         required: false
 394 |       },
 395 |       {
 396 |         name: 'integration_needs',
 397 |         description: 'External systems to integrate with',
 398 |         required: false
 399 |       },
 400 |       {
 401 |         name: 'compliance_requirements',
 402 |         description: 'Data compliance needs (gdpr, hipaa, sox, none)',
 403 |         required: false
 404 |       }
 405 |     ]
 406 |   },
 407 |   {
 408 |     name: 'data_quality_audit',
 409 |     description: 'Comprehensive AI-powered data quality assessment with cleansing recommendations',
 410 |     arguments: [
 411 |       {
 412 |         name: 'tables',
 413 |         description: 'Tables to audit (comma-separated or "all")',
 414 |         required: true
 415 |       },
 416 |       {
 417 |         name: 'quality_dimensions',
 418 |         description: 'Quality aspects to check (completeness, accuracy, consistency, validity, uniqueness, timeliness)',
 419 |         required: false
 420 |       },
 421 |       {
 422 |         name: 'severity_threshold',
 423 |         description: 'Minimum severity level to report (low, medium, high, critical)',
 424 |         required: false
 425 |       },
 426 |       {
 427 |         name: 'auto_fix_suggestions',
 428 |         description: 'Include automated fix suggestions (true/false)',
 429 |         required: false
 430 |       }
 431 |     ]
 432 |   },
 433 |   {
 434 |     name: 'predictive_analytics',
 435 |     description: 'Advanced predictive modeling and forecasting using historical Airtable data',
 436 |     arguments: [
 437 |       {
 438 |         name: 'table',
 439 |         description: 'Table containing historical data',
 440 |         required: true
 441 |       },
 442 |       {
 443 |         name: 'target_field',
 444 |         description: 'Field to predict or forecast',
 445 |         required: true
 446 |       },
 447 |       {
 448 |         name: 'prediction_horizon',
 449 |         description: 'Forecast period (next_week, next_month, next_quarter, next_year)',
 450 |         required: false
 451 |       },
 452 |       {
 453 |         name: 'model_type',
 454 |         description: 'Prediction model (trend_analysis, seasonal_forecast, regression, classification, time_series)',
 455 |         required: false
 456 |       },
 457 |       {
 458 |         name: 'feature_fields',
 459 |         description: 'Fields to use as predictive features',
 460 |         required: false
 461 |       }
 462 |     ]
 463 |   },
 464 |   {
 465 |     name: 'natural_language_query',
 466 |     description: 'Process natural language questions about your data and provide intelligent answers',
 467 |     arguments: [
 468 |       {
 469 |         name: 'question',
 470 |         description: 'Natural language question about your data',
 471 |         required: true
 472 |       },
 473 |       {
 474 |         name: 'context_tables',
 475 |         description: 'Tables that might contain relevant data',
 476 |         required: false
 477 |       },
 478 |       {
 479 |         name: 'response_format',
 480 |         description: 'Desired response format (narrative, data_summary, visualization_suggestion, action_items)',
 481 |         required: false
 482 |       },
 483 |       {
 484 |         name: 'include_confidence',
 485 |         description: 'Include confidence scores for answers (true/false)',
 486 |         required: false
 487 |       }
 488 |     ]
 489 |   },
 490 |   {
 491 |     name: 'smart_data_transformation',
 492 |     description: 'AI-assisted data transformation, cleaning, and enrichment with intelligent suggestions',
 493 |     arguments: [
 494 |       {
 495 |         name: 'source_table',
 496 |         description: 'Source table for transformation',
 497 |         required: true
 498 |       },
 499 |       {
 500 |         name: 'transformation_goal',
 501 |         description: 'Goal (normalize, standardize, enrich, cleanse, aggregate, pivot)',
 502 |         required: true
 503 |       },
 504 |       {
 505 |         name: 'target_format',
 506 |         description: 'Desired output format or structure',
 507 |         required: false
 508 |       },
 509 |       {
 510 |         name: 'quality_rules',
 511 |         description: 'Data quality rules to apply during transformation',
 512 |         required: false
 513 |       },
 514 |       {
 515 |         name: 'preserve_history',
 516 |         description: 'Maintain audit trail of changes (true/false)',
 517 |         required: false
 518 |       }
 519 |     ]
 520 |   },
 521 |   {
 522 |     name: 'automation_recommendations',
 523 |     description: 'Generate intelligent automation suggestions based on workflow patterns and data analysis',
 524 |     arguments: [
 525 |       {
 526 |         name: 'workflow_description',
 527 |         description: 'Description of current manual processes',
 528 |         required: false
 529 |       },
 530 |       {
 531 |         name: 'automation_scope',
 532 |         description: 'Scope (single_table, multi_table, cross_base, external_integration)',
 533 |         required: false
 534 |       },
 535 |       {
 536 |         name: 'frequency_patterns',
 537 |         description: 'How often tasks are performed',
 538 |         required: false
 539 |       },
 540 |       {
 541 |         name: 'complexity_tolerance',
 542 |         description: 'Acceptable automation complexity (simple, moderate, advanced)',
 543 |         required: false
 544 |       },
 545 |       {
 546 |         name: 'integration_capabilities',
 547 |         description: 'Available integration tools (zapier, make, custom_api, native_automations)',
 548 |         required: false
 549 |       }
 550 |     ]
 551 |   }
 552 | ];
 553 | 
 554 | // Roots configuration for filesystem access
 555 | const ROOTS_CONFIG = [
 556 |   {
 557 |     uri: 'file:///airtable-exports',
 558 |     name: 'Airtable Exports'
 559 |   },
 560 |   {
 561 |     uri: 'file:///airtable-attachments', 
 562 |     name: 'Airtable Attachments'
 563 |   }
 564 | ];
 565 | 
 566 | // Logging configuration (currentLogLevel is already declared above)
 567 | 
 568 | // HTTP server
 569 | const server = http.createServer(async (req, res) => {
 570 |   // Security headers
 571 |   res.setHeader('X-Content-Type-Options', 'nosniff');
 572 |   res.setHeader('X-Frame-Options', 'DENY');
 573 |   res.setHeader('X-XSS-Protection', '1; mode=block');
 574 |   res.setHeader('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGINS || '*');
 575 |   res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
 576 |   res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
 577 |   
 578 |   // Handle preflight request
 579 |   if (req.method === 'OPTIONS') {
 580 |     res.writeHead(200);
 581 |     res.end();
 582 |     return;
 583 |   }
 584 |   
 585 |   const parsedUrl = url.parse(req.url, true);
 586 |   const pathname = parsedUrl.pathname;
 587 |   
 588 |   // Health check endpoint
 589 |   if (pathname === '/health' && req.method === 'GET') {
 590 |     res.writeHead(200, { 'Content-Type': 'application/json' });
 591 |     res.end(JSON.stringify({
 592 |       status: 'healthy',
 593 |       version: '3.0.0',
 594 |       timestamp: new Date().toISOString(),
 595 |       uptime: process.uptime()
 596 |     }));
 597 |     return;
 598 |   }
 599 |   
 600 |   // OAuth2 authorization endpoint
 601 |   if (pathname === '/oauth/authorize' && req.method === 'GET') {
 602 |     const params = parsedUrl.query;
 603 |     const clientId = params.client_id;
 604 |     const redirectUri = params.redirect_uri;
 605 |     const state = params.state;
 606 |     const codeChallenge = params.code_challenge;
 607 |     const codeChallengeMethod = params.code_challenge_method;
 608 |     
 609 |     // Validate inputs to prevent XSS
 610 |     if (!clientId || !redirectUri) {
 611 |       res.writeHead(400, { 'Content-Type': 'application/json' });
 612 |       res.end(JSON.stringify({ error: 'invalid_request', error_description: 'Missing required parameters' }));
 613 |       return;
 614 |     }
 615 |     
 616 |     // Validate redirect URI
 617 |     if (!validateUrl(redirectUri)) {
 618 |       res.writeHead(400, { 'Content-Type': 'application/json' });
 619 |       res.end(JSON.stringify({ error: 'invalid_request', error_description: 'Invalid redirect URI' }));
 620 |       return;
 621 |     }
 622 |     
 623 |     // Create safe copies of all variables for JavaScript use
 624 |     const safeRedirectUri = redirectUri.slice(0, 2000); // Limit length
 625 |     const safeState = (state || '').slice(0, 200); // Limit length
 626 |     const safeClientId = clientId.slice(0, 200); // Limit length
 627 |     
 628 |     // Sanitize for HTML display only
 629 |     const displayClientId = escapeHtml(safeClientId);
 630 |     const displayRedirectUri = escapeHtml(safeRedirectUri);
 631 |     
 632 |     // Generate authorization code
 633 |     const authCode = crypto.randomBytes(32).toString('hex');
 634 |     
 635 |     // In a real implementation, store the auth code with expiration
 636 |     // and associate it with the client and PKCE challenge
 637 |     
 638 |     res.writeHead(200, { 
 639 |       'Content-Type': 'text/html; charset=utf-8',
 640 |       'Content-Security-Policy': "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none';",
 641 |       'X-Content-Type-Options': 'nosniff',
 642 |       'X-Frame-Options': 'DENY',
 643 |       'X-XSS-Protection': '1; mode=block',
 644 |       'Referrer-Policy': 'no-referrer',
 645 |       'Cache-Control': 'no-store, no-cache, must-revalidate, private'
 646 |     });
 647 |     
 648 |     // Safely encode data for embedding in HTML attributes and JavaScript
 649 |     // This prevents XSS by encoding any potentially dangerous characters
 650 |     const safeJsonConfig = JSON.stringify({
 651 |       redirectUri: safeRedirectUri,
 652 |       code: authCode,
 653 |       state: safeState,
 654 |       clientId: displayClientId,
 655 |       displayRedirectUri: displayRedirectUri
 656 |     }).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026').replace(/'/g, '\\u0027').replace(/"/g, '\\u0022');
 657 |     
 658 |     // Build HTML with all dynamic content properly escaped
 659 |     // Using template literals but with pre-escaped content only
 660 |     const htmlContent = `<!DOCTYPE html>
 661 | <html>
 662 | <head>
 663 |   <meta charset="UTF-8">
 664 |   <title>OAuth2 Authorization</title>
 665 |   <meta name="viewport" content="width=device-width, initial-scale=1.0">
 666 |   <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline';">
 667 | </head>
 668 | <body>
 669 |   <h2>Airtable MCP Server - OAuth2 Authorization</h2>
 670 |   <p>Client ID: <span id="client-id"></span></p>
 671 |   <p>Redirect URI: <span id="redirect-uri"></span></p>
 672 |   <div style="margin: 20px 0;">
 673 |     <button onclick="authorize()" style="background: #18BFFF; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer;">
 674 |       Authorize Application
 675 |     </button>
 676 |     <button onclick="deny()" style="background: #ff4444; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin-left: 10px;">
 677 |       Deny Access
 678 |     </button>
 679 |   </div>
 680 |   <script>
 681 |     // Parse safely encoded JSON config
 682 |     (function() {
 683 |       // Config is safely encoded to prevent XSS
 684 |       var config = ${safeJsonConfig};
 685 |       
 686 |       // Safely set text content (not innerHTML) to prevent XSS
 687 |       document.addEventListener('DOMContentLoaded', function() {
 688 |         document.getElementById('client-id').textContent = config.clientId;
 689 |         document.getElementById('redirect-uri').textContent = config.displayRedirectUri;
 690 |       });
 691 |       
 692 |       window.authorize = function() {
 693 |         try {
 694 |           var url = new URL(config.redirectUri);
 695 |           if (url.protocol !== 'http:' && url.protocol !== 'https:') {
 696 |             throw new Error('Invalid protocol');
 697 |           }
 698 |           var finalUrl = config.redirectUri + '?code=' + encodeURIComponent(config.code) + '&state=' + encodeURIComponent(config.state);
 699 |           window.location.href = finalUrl;
 700 |         } catch (e) {
 701 |           console.error('Authorization failed:', e);
 702 |           alert('Invalid redirect URL');
 703 |         }
 704 |       };
 705 |       
 706 |       window.deny = function() {
 707 |         try {
 708 |           var url = new URL(config.redirectUri);
 709 |           if (url.protocol !== 'http:' && url.protocol !== 'https:') {
 710 |             throw new Error('Invalid protocol');
 711 |           }
 712 |           var finalUrl = config.redirectUri + '?error=access_denied&state=' + encodeURIComponent(config.state);
 713 |           window.location.href = finalUrl;
 714 |         } catch (e) {
 715 |           console.error('Denial failed:', e);
 716 |           alert('Invalid redirect URL');
 717 |         }
 718 |       };
 719 |     })();
 720 |   </script>
 721 | </body>
 722 | </html>`;
 723 | 
 724 |     // Write response with explicit UTF-8 encoding
 725 |     res.end(htmlContent, 'utf8');
 726 |     return;
 727 |   }
 728 |   
 729 |   // OAuth2 token endpoint
 730 |   if (pathname === '/oauth/token' && req.method === 'POST') {
 731 |     let body = '';
 732 |     req.on('data', chunk => {
 733 |       body += chunk.toString();
 734 |       // Prevent DoS by limiting body size
 735 |       if (body.length > 10000) {
 736 |         res.writeHead(413, { 'Content-Type': 'application/json' });
 737 |         res.end(JSON.stringify({ error: 'payload_too_large', error_description: 'Request body too large' }));
 738 |         return;
 739 |       }
 740 |     });
 741 |     
 742 |     req.on('end', () => {
 743 |       try {
 744 |         const params = querystring.parse(body);
 745 |         const grantType = sanitizeInput(params.grant_type);
 746 |         const code = sanitizeInput(params.code);
 747 |         const codeVerifier = sanitizeInput(params.code_verifier);
 748 |         const clientId = sanitizeInput(params.client_id);
 749 |         
 750 |         // Validate required parameters
 751 |         if (!grantType || !code || !clientId) {
 752 |           res.writeHead(400, { 'Content-Type': 'application/json' });
 753 |           res.end(JSON.stringify({
 754 |             error: 'invalid_request',
 755 |             error_description: 'Missing required parameters'
 756 |           }));
 757 |           return;
 758 |         }
 759 |         
 760 |         // In a real implementation, verify the authorization code and PKCE
 761 |         if (grantType === 'authorization_code' && code) {
 762 |           // Generate access token
 763 |           const accessToken = crypto.randomBytes(32).toString('hex');
 764 |           const refreshToken = crypto.randomBytes(32).toString('hex');
 765 |           
 766 |           res.writeHead(200, { 
 767 |             'Content-Type': 'application/json',
 768 |             'Cache-Control': 'no-store',
 769 |             'Pragma': 'no-cache'
 770 |           });
 771 |           res.end(JSON.stringify({
 772 |             access_token: accessToken,
 773 |             token_type: 'Bearer',
 774 |             expires_in: 3600,
 775 |             refresh_token: refreshToken,
 776 |             scope: 'data.records:read data.records:write schema.bases:read'
 777 |           }));
 778 |         } else {
 779 |           res.writeHead(400, { 'Content-Type': 'application/json' });
 780 |           res.end(JSON.stringify({
 781 |             error: 'invalid_grant',
 782 |             error_description: 'Invalid grant type or authorization code'
 783 |           }));
 784 |         }
 785 |       } catch (error) {
 786 |         log(LOG_LEVELS.WARN, 'OAuth token request parsing failed', { error: error.message });
 787 |         res.writeHead(400, { 'Content-Type': 'application/json' });
 788 |         res.end(JSON.stringify({
 789 |           error: 'invalid_request',
 790 |           error_description: 'Malformed request body'
 791 |         }));
 792 |       }
 793 |     });
 794 |     return;
 795 |   }
 796 |   
 797 |   // MCP endpoint
 798 |   if (pathname === '/mcp' && req.method === 'POST') {
 799 |     // Rate limiting
 800 |     const clientId = req.headers['x-client-id'] || req.connection.remoteAddress;
 801 |     if (!checkRateLimit(clientId)) {
 802 |       res.writeHead(429, { 'Content-Type': 'application/json' });
 803 |       res.end(JSON.stringify({
 804 |         jsonrpc: '2.0',
 805 |         error: {
 806 |           code: -32000,
 807 |           message: 'Rate limit exceeded. Maximum 60 requests per minute.'
 808 |         }
 809 |       }));
 810 |       return;
 811 |     }
 812 |     
 813 |     let body = '';
 814 |     req.on('data', chunk => body += chunk.toString());
 815 |     
 816 |     req.on('end', async () => {
 817 |       try {
 818 |         const request = JSON.parse(body);
 819 |         
 820 |         // Sanitize inputs
 821 |         if (request.params) {
 822 |           Object.keys(request.params).forEach(key => {
 823 |             request.params[key] = sanitizeInput(request.params[key]);
 824 |           });
 825 |         }
 826 |         
 827 |         log(LOG_LEVELS.DEBUG, 'MCP request received', { 
 828 |           method: request.method, 
 829 |           id: request.id 
 830 |         });
 831 |         
 832 |         let response;
 833 |         
 834 |         switch (request.method) {
 835 |           case 'initialize':
 836 |             response = {
 837 |               jsonrpc: '2.0',
 838 |               id: request.id,
 839 |               result: {
 840 |                 protocolVersion: '2024-11-05',
 841 |                 capabilities: {
 842 |                   tools: { listChanged: true },
 843 |                   resources: { subscribe: true, listChanged: true },
 844 |                   prompts: { listChanged: true },
 845 |                   sampling: {},
 846 |                   roots: { listChanged: true },
 847 |                   logging: {}
 848 |                 },
 849 |                 serverInfo: {
 850 |                   name: 'Airtable MCP Server - AI Agent Enhanced',
 851 |                   version: '3.0.0',
 852 |                   description: 'Advanced AI-powered MCP server with 10 intelligent prompt templates, predictive analytics, and enterprise automation capabilities'
 853 |                 }
 854 |               }
 855 |             };
 856 |             log(LOG_LEVELS.INFO, 'Client initialized', { clientId: request.id });
 857 |             break;
 858 |             
 859 |           case 'tools/list':
 860 |             response = {
 861 |               jsonrpc: '2.0',
 862 |               id: request.id,
 863 |               result: {
 864 |                 tools: TOOLS_SCHEMA
 865 |               }
 866 |             };
 867 |             break;
 868 |             
 869 |           case 'tools/call':
 870 |             response = await handleToolCall(request);
 871 |             break;
 872 |             
 873 |           case 'prompts/list':
 874 |             response = {
 875 |               jsonrpc: '2.0',
 876 |               id: request.id,
 877 |               result: {
 878 |                 prompts: PROMPTS_SCHEMA
 879 |               }
 880 |             };
 881 |             break;
 882 |             
 883 |           case 'prompts/get':
 884 |             response = await handlePromptGet(request);
 885 |             break;
 886 |             
 887 |           case 'roots/list':
 888 |             response = {
 889 |               jsonrpc: '2.0',
 890 |               id: request.id,
 891 |               result: {
 892 |                 roots: ROOTS_CONFIG
 893 |               }
 894 |             };
 895 |             break;
 896 |             
 897 |           case 'logging/setLevel':
 898 |             const level = request.params?.level;
 899 |             if (level && LOG_LEVELS[level.toUpperCase()] !== undefined) {
 900 |               currentLogLevel = LOG_LEVELS[level.toUpperCase()];
 901 |               log(LOG_LEVELS.INFO, 'Log level updated', { newLevel: level });
 902 |             }
 903 |             response = {
 904 |               jsonrpc: '2.0',
 905 |               id: request.id,
 906 |               result: {}
 907 |             };
 908 |             break;
 909 |             
 910 |           case 'sampling/createMessage':
 911 |             response = await handleSampling(request);
 912 |             break;
 913 |             
 914 |           default:
 915 |             log(LOG_LEVELS.WARN, 'Unknown method', { method: request.method });
 916 |             throw new Error(`Method "${request.method}" not found`);
 917 |         }
 918 |         
 919 |         res.writeHead(200, { 'Content-Type': 'application/json' });
 920 |         res.end(JSON.stringify(response));
 921 |         
 922 |       } catch (error) {
 923 |         log(LOG_LEVELS.ERROR, 'Request processing failed', { error: error.message });
 924 |         
 925 |         const errorResponse = {
 926 |           jsonrpc: '2.0',
 927 |           id: request?.id || null,
 928 |           error: {
 929 |             code: -32000,
 930 |             message: error.message || 'Internal server error'
 931 |           }
 932 |         };
 933 |         
 934 |         res.writeHead(200, { 'Content-Type': 'application/json' });
 935 |         res.end(JSON.stringify(errorResponse));
 936 |       }
 937 |     });
 938 |     return;
 939 |   }
 940 |   
 941 |   // Default 404
 942 |   res.writeHead(404, { 'Content-Type': 'application/json' });
 943 |   res.end(JSON.stringify({ error: 'Not Found' }));
 944 | });
 945 | 
 946 | // Tool handlers
 947 | async function handleToolCall(request) {
 948 |   const toolName = request.params.name;
 949 |   const toolParams = request.params.arguments || {};
 950 |   
 951 |   try {
 952 |     let result;
 953 |     let responseText;
 954 |     
 955 |     switch (toolName) {
 956 |       case 'list_tables':
 957 |         const includeSchema = toolParams.include_schema || false;
 958 |         result = await callAirtableAPI(`meta/bases/${baseId}/tables`);
 959 |         const tables = result.tables || [];
 960 |         
 961 |         responseText = tables.length > 0 
 962 |           ? `Found ${tables.length} table(s): ` + 
 963 |             tables.map((table, i) => 
 964 |               `${table.name} (ID: ${table.id}, Fields: ${table.fields?.length || 0})`
 965 |             ).join(', ')
 966 |           : 'No tables found in this base.';
 967 |         break;
 968 |         
 969 |       case 'list_records':
 970 |         const { table, maxRecords, view, filterByFormula } = toolParams;
 971 |         
 972 |         const queryParams = {};
 973 |         if (maxRecords) queryParams.maxRecords = maxRecords;
 974 |         if (view) queryParams.view = view;
 975 |         if (filterByFormula) queryParams.filterByFormula = filterByFormula;
 976 |         
 977 |         result = await callAirtableAPI(table, 'GET', null, queryParams);
 978 |         const records = result.records || [];
 979 |         
 980 |         responseText = records.length > 0
 981 |           ? `Found ${records.length} record(s) in table "${table}"`
 982 |           : `No records found in table "${table}".`;
 983 |         break;
 984 |         
 985 |       case 'get_record':
 986 |         const { table: getTable, recordId } = toolParams;
 987 |         result = await callAirtableAPI(`${getTable}/${recordId}`);
 988 |         responseText = `Retrieved record ${recordId} from table "${getTable}"`;
 989 |         break;
 990 |         
 991 |       case 'create_record':
 992 |         const { table: createTable, fields } = toolParams;
 993 |         const body = { fields: fields };
 994 |         result = await callAirtableAPI(createTable, 'POST', body);
 995 |         responseText = `Successfully created record in table "${createTable}" with ID: ${result.id}`;
 996 |         break;
 997 |         
 998 |       case 'update_record':
 999 |         const { table: updateTable, recordId: updateRecordId, fields: updateFields } = toolParams;
1000 |         const updateBody = { fields: updateFields };
1001 |         result = await callAirtableAPI(`${updateTable}/${updateRecordId}`, 'PATCH', updateBody);
1002 |         responseText = `Successfully updated record ${updateRecordId} in table "${updateTable}"`;
1003 |         break;
1004 |         
1005 |       case 'delete_record':
1006 |         const { table: deleteTable, recordId: deleteRecordId } = toolParams;
1007 |         result = await callAirtableAPI(`${deleteTable}/${deleteRecordId}`, 'DELETE');
1008 |         responseText = `Successfully deleted record ${deleteRecordId} from table "${deleteTable}"`;
1009 |         break;
1010 |         
1011 |       default:
1012 |         throw new Error(`Unknown tool: ${toolName}`);
1013 |     }
1014 |     
1015 |     return {
1016 |       jsonrpc: '2.0',
1017 |       id: request.id,
1018 |       result: {
1019 |         content: [
1020 |           {
1021 |             type: 'text',
1022 |             text: responseText
1023 |           }
1024 |         ]
1025 |       }
1026 |     };
1027 |     
1028 |   } catch (error) {
1029 |     log(LOG_LEVELS.ERROR, `Tool ${toolName} failed`, { error: error.message });
1030 |     
1031 |     return {
1032 |       jsonrpc: '2.0',
1033 |       id: request.id,
1034 |       result: {
1035 |         content: [
1036 |           {
1037 |             type: 'text',
1038 |             text: `Error executing ${toolName}: ${error.message}`
1039 |           }
1040 |         ]
1041 |       }
1042 |     };
1043 |   }
1044 | }
1045 | 
1046 | // Enhanced AI-powered prompt handlers
1047 | async function handlePromptGet(request) {
1048 |   const promptName = request.params.name;
1049 |   const promptArgs = request.params.arguments || {};
1050 |   
1051 |   try {
1052 |     const prompt = PROMPTS_SCHEMA.find(p => p.name === promptName);
1053 |     if (!prompt) {
1054 |       throw new Error(`Prompt "${promptName}" not found`);
1055 |     }
1056 |     
1057 |     let messages = [];
1058 |     
1059 |     switch (promptName) {
1060 |       case 'analyze_data':
1061 |         const { table, analysis_type = 'statistical', field_focus, time_dimension, confidence_level = '0.95' } = promptArgs;
1062 |         messages = [
1063 |           {
1064 |             role: 'user',
1065 |             content: {
1066 |               type: 'text',
1067 |               text: `🤖 ADVANCED DATA ANALYSIS REQUEST
1068 | 
1069 | **Table**: ${table}
1070 | **Analysis Type**: ${analysis_type}
1071 | **Confidence Level**: ${confidence_level}
1072 | ${field_focus ? `**Focus Fields**: ${field_focus}` : ''}
1073 | ${time_dimension ? `**Time Dimension**: ${time_dimension}` : ''}
1074 | 
1075 | **Instructions**:
1076 | 1. First, examine the table schema and structure using list_tables with include_schema=true
1077 | 2. Retrieve representative sample data using list_records with appropriate filters
1078 | 3. Perform ${analysis_type} analysis with statistical rigor
1079 | 4. Generate insights with confidence intervals and significance testing
1080 | 5. Provide actionable recommendations based on findings
1081 | 
1082 | **Expected Deliverables**:
1083 | - Statistical summary with key metrics
1084 | - Pattern identification and trend analysis
1085 | - Anomaly detection if applicable
1086 | - Predictive insights where relevant
1087 | - Visualization recommendations
1088 | - Business impact assessment
1089 | 
1090 | Please use the available Airtable tools to gather data and provide comprehensive ${analysis_type} analysis.`
1091 |             }
1092 |           }
1093 |         ];
1094 |         break;
1095 |         
1096 |       case 'create_report':
1097 |         const { table: reportTable, report_type = 'executive_summary', time_period = 'last_30_days', stakeholder_level = 'manager', include_recommendations = 'true' } = promptArgs;
1098 |         messages = [
1099 |           {
1100 |             role: 'user',
1101 |             content: {
1102 |               type: 'text',
1103 |               text: `📊 INTELLIGENT REPORT GENERATION
1104 | 
1105 | **Target Table**: ${reportTable}
1106 | **Report Type**: ${report_type}
1107 | **Time Period**: ${time_period}
1108 | **Stakeholder Level**: ${stakeholder_level}
1109 | **Include Recommendations**: ${include_recommendations}
1110 | 
1111 | **Report Generation Process**:
1112 | 1. Analyze table structure and data types
1113 | 2. Extract relevant data for specified time period
1114 | 3. Calculate key performance indicators
1115 | 4. Identify trends and patterns
1116 | 5. Generate visualizations suggestions
1117 | 6. Create ${stakeholder_level}-appropriate narrative
1118 | 
1119 | **Report Sections**:
1120 | - Executive Summary (key findings)
1121 | - Data Overview and Quality Assessment
1122 | - Trend Analysis and Patterns
1123 | - Performance Metrics and KPIs
1124 | - Risk Assessment and Opportunities
1125 | ${include_recommendations === 'true' ? '- AI-Generated Recommendations' : ''}
1126 | - Next Steps and Action Items
1127 | 
1128 | Please gather the necessary data and create a comprehensive ${report_type} tailored for ${stakeholder_level} level stakeholders.`
1129 |             }
1130 |           }
1131 |         ];
1132 |         break;
1133 |         
1134 |       case 'data_insights':
1135 |         const { tables, insight_type = 'correlations', business_context = 'general', insight_depth = 'moderate' } = promptArgs;
1136 |         messages = [
1137 |           {
1138 |             role: 'user',
1139 |             content: {
1140 |               type: 'text',
1141 |               text: `🔍 ADVANCED DATA INSIGHTS DISCOVERY
1142 | 
1143 | **Target Tables**: ${tables}
1144 | **Insight Type**: ${insight_type}
1145 | **Business Context**: ${business_context}
1146 | **Analysis Depth**: ${insight_depth}
1147 | 
1148 | **Discovery Framework**:
1149 | 1. Multi-table schema analysis and relationship mapping
1150 | 2. Cross-table data correlation analysis
1151 | 3. Pattern recognition using ${business_context} domain knowledge
1152 | 4. Statistical significance testing
1153 | 5. Business impact quantification
1154 | 
1155 | **Insight Categories**:
1156 | - ${insight_type} analysis with statistical validation
1157 | - Hidden patterns and unexpected relationships
1158 | - Segmentation opportunities
1159 | - Predictive indicators
1160 | - Data quality insights
1161 | - Business optimization opportunities
1162 | 
1163 | **${business_context.toUpperCase()} CONTEXT ANALYSIS**:
1164 | ${business_context === 'sales' ? '- Revenue drivers and conversion patterns\n- Customer lifetime value indicators\n- Sales cycle optimization opportunities' : ''}
1165 | ${business_context === 'marketing' ? '- Campaign effectiveness and attribution\n- Customer segmentation insights\n- Channel performance analysis' : ''}
1166 | ${business_context === 'operations' ? '- Process efficiency metrics\n- Resource utilization patterns\n- Bottleneck identification' : ''}
1167 | 
1168 | Please conduct ${insight_depth} analysis across the specified tables and provide actionable business insights.`
1169 |             }
1170 |           }
1171 |         ];
1172 |         break;
1173 |         
1174 |       case 'optimize_workflow':
1175 |         const { base_overview, optimization_focus = 'automation', current_pain_points, team_size } = promptArgs;
1176 |         messages = [
1177 |           {
1178 |             role: 'user',
1179 |             content: {
1180 |               type: 'text',
1181 |               text: `⚡ AI-POWERED WORKFLOW OPTIMIZATION
1182 | 
1183 | **Optimization Focus**: ${optimization_focus}
1184 | **Team Size**: ${team_size || 'Not specified'}
1185 | ${base_overview ? `**Base Overview**: ${base_overview}` : ''}
1186 | ${current_pain_points ? `**Current Pain Points**: ${current_pain_points}` : ''}
1187 | 
1188 | **Optimization Analysis**:
1189 | 1. Workflow pattern analysis and bottleneck identification
1190 | 2. Automation opportunity assessment
1191 | 3. User experience and efficiency evaluation
1192 | 4. Integration and scaling considerations
1193 | 5. ROI analysis for proposed improvements
1194 | 
1195 | **${optimization_focus.toUpperCase()} OPTIMIZATION**:
1196 | ${optimization_focus === 'automation' ? '- Identify repetitive manual tasks\n- Suggest automation workflows\n- Estimate time savings and ROI' : ''}
1197 | ${optimization_focus === 'data_quality' ? '- Data validation and cleansing rules\n- Consistency and accuracy improvements\n- Quality monitoring systems' : ''}
1198 | ${optimization_focus === 'collaboration' ? '- Team workflow improvements\n- Permission and access optimization\n- Communication enhancement strategies' : ''}
1199 | 
1200 | **Deliverables**:
1201 | - Workflow efficiency assessment
1202 | - Prioritized improvement recommendations
1203 | - Implementation roadmap with timelines
1204 | - Cost-benefit analysis
1205 | - Change management considerations
1206 | 
1207 | Please analyze the current setup and provide comprehensive ${optimization_focus} optimization recommendations.`
1208 |             }
1209 |           }
1210 |         ];
1211 |         break;
1212 | 
1213 |       case 'smart_schema_design':
1214 |         const { use_case, data_volume = 'medium', integration_needs, compliance_requirements = 'none' } = promptArgs;
1215 |         messages = [
1216 |           {
1217 |             role: 'user',
1218 |             content: {
1219 |               type: 'text',
1220 |               text: `🏗️ AI-ASSISTED SCHEMA OPTIMIZATION
1221 | 
1222 | **Use Case**: ${use_case}
1223 | **Data Volume**: ${data_volume}
1224 | **Compliance**: ${compliance_requirements}
1225 | ${integration_needs ? `**Integrations**: ${integration_needs}` : ''}
1226 | 
1227 | **Schema Design Analysis**:
1228 | 1. Current schema evaluation for ${use_case} best practices
1229 | 2. Field type and relationship optimization
1230 | 3. Performance and scalability assessment
1231 | 4. Compliance requirement implementation
1232 | 5. Integration compatibility review
1233 | 
1234 | **${use_case.toUpperCase()} OPTIMIZATION**:
1235 | ${use_case === 'crm' ? '- Customer lifecycle tracking\n- Sales pipeline optimization\n- Contact relationship mapping' : ''}
1236 | ${use_case === 'project_management' ? '- Task dependency modeling\n- Resource allocation tracking\n- Timeline and milestone management' : ''}
1237 | ${use_case === 'inventory' ? '- Stock level monitoring\n- Supplier relationship tracking\n- Cost and pricing optimization' : ''}
1238 | 
1239 | **Recommendations**:
1240 | - Optimal field types and relationships
1241 | - Indexing and performance suggestions
1242 | - Data validation and integrity rules
1243 | - Automation and workflow triggers
1244 | - Scaling and maintenance considerations
1245 | 
1246 | Please analyze the current schema and provide ${use_case}-optimized recommendations.`
1247 |             }
1248 |           }
1249 |         ];
1250 |         break;
1251 | 
1252 |       case 'data_quality_audit':
1253 |         const { tables: auditTables, quality_dimensions = 'completeness,accuracy,consistency', severity_threshold = 'medium', auto_fix_suggestions = 'true' } = promptArgs;
1254 |         messages = [
1255 |           {
1256 |             role: 'user',
1257 |             content: {
1258 |               type: 'text',
1259 |               text: `🔍 COMPREHENSIVE DATA QUALITY AUDIT
1260 | 
1261 | **Tables to Audit**: ${auditTables}
1262 | **Quality Dimensions**: ${quality_dimensions}
1263 | **Severity Threshold**: ${severity_threshold}
1264 | **Auto-Fix Suggestions**: ${auto_fix_suggestions}
1265 | 
1266 | **Audit Framework**:
1267 | 1. Data completeness analysis (missing values, empty fields)
1268 | 2. Accuracy assessment (format validation, range checks)
1269 | 3. Consistency evaluation (cross-field validation, duplicates)
1270 | 4. Validity verification (data type compliance, constraints)
1271 | 5. Uniqueness analysis (duplicate detection, key integrity)
1272 | 6. Timeliness review (data freshness, update patterns)
1273 | 
1274 | **Quality Assessment Process**:
1275 | - Statistical analysis of data distribution
1276 | - Pattern recognition for anomalies
1277 | - Cross-table consistency validation
1278 | - Historical trend analysis
1279 | - Business rule compliance checking
1280 | 
1281 | **Deliverables**:
1282 | - Quality score by dimension and table
1283 | - Detailed issue identification and classification
1284 | - Impact assessment and prioritization
1285 | ${auto_fix_suggestions === 'true' ? '- Automated fix suggestions and scripts' : ''}
1286 | - Data governance recommendations
1287 | - Monitoring and maintenance strategies
1288 | 
1289 | Please conduct a thorough data quality audit focusing on ${quality_dimensions} dimensions.`
1290 |             }
1291 |           }
1292 |         ];
1293 |         break;
1294 | 
1295 |       case 'predictive_analytics':
1296 |         const { table: predTable, target_field, prediction_horizon = 'next_month', model_type = 'trend_analysis', feature_fields } = promptArgs;
1297 |         messages = [
1298 |           {
1299 |             role: 'user',
1300 |             content: {
1301 |               type: 'text',
1302 |               text: `🔮 ADVANCED PREDICTIVE ANALYTICS
1303 | 
1304 | **Source Table**: ${predTable}
1305 | **Target Field**: ${target_field}
1306 | **Prediction Horizon**: ${prediction_horizon}
1307 | **Model Type**: ${model_type}
1308 | ${feature_fields ? `**Feature Fields**: ${feature_fields}` : ''}
1309 | 
1310 | **Predictive Modeling Process**:
1311 | 1. Historical data analysis and trend identification
1312 | 2. Feature engineering and variable selection
1313 | 3. Model development using ${model_type} approach
1314 | 4. Validation and accuracy assessment
1315 | 5. Forecast generation for ${prediction_horizon}
1316 | 6. Confidence intervals and uncertainty quantification
1317 | 
1318 | **${model_type.toUpperCase()} ANALYSIS**:
1319 | ${model_type === 'time_series' ? '- Seasonal pattern detection\n- Trend decomposition\n- Cyclical behavior analysis' : ''}
1320 | ${model_type === 'regression' ? '- Variable relationship modeling\n- Predictive factor identification\n- Statistical significance testing' : ''}
1321 | ${model_type === 'classification' ? '- Category prediction modeling\n- Feature importance analysis\n- Classification accuracy metrics' : ''}
1322 | 
1323 | **Outputs**:
1324 | - Historical pattern analysis
1325 | - Predictive model performance metrics
1326 | - Forecast values with confidence intervals
1327 | - Key influencing factors identification
1328 | - Model limitations and assumptions
1329 | - Actionable insights and recommendations
1330 | 
1331 | Please develop a ${model_type} model to predict ${target_field} over ${prediction_horizon}.`
1332 |             }
1333 |           }
1334 |         ];
1335 |         break;
1336 | 
1337 |       case 'natural_language_query':
1338 |         const { question, context_tables, response_format = 'narrative', include_confidence = 'true' } = promptArgs;
1339 |         messages = [
1340 |           {
1341 |             role: 'user',
1342 |             content: {
1343 |               type: 'text',
1344 |               text: `🗣️ NATURAL LANGUAGE DATA QUERY
1345 | 
1346 | **Question**: "${question}"
1347 | ${context_tables ? `**Context Tables**: ${context_tables}` : ''}
1348 | **Response Format**: ${response_format}
1349 | **Include Confidence**: ${include_confidence}
1350 | 
1351 | **Query Processing Framework**:
1352 | 1. Question analysis and intent recognition
1353 | 2. Relevant table and field identification
1354 | 3. Data retrieval strategy formulation
1355 | 4. Analysis execution and result compilation
1356 | 5. Natural language response generation
1357 | 
1358 | **Analysis Approach**:
1359 | - Semantic understanding of the question
1360 | - Automatic table and field mapping
1361 | - Intelligent data filtering and aggregation
1362 | - Statistical analysis where appropriate
1363 | - Context-aware interpretation
1364 | 
1365 | **Response Requirements**:
1366 | ${response_format === 'narrative' ? '- Conversational, easy-to-understand explanation\n- Supporting data and evidence\n- Contextual insights and implications' : ''}
1367 | ${response_format === 'data_summary' ? '- Structured data summary\n- Key metrics and statistics\n- Trend identification' : ''}
1368 | ${response_format === 'visualization_suggestion' ? '- Chart and graph recommendations\n- Data visualization best practices\n- Tool-specific guidance' : ''}
1369 | ${include_confidence === 'true' ? '\n- Confidence scores for answers\n- Data quality indicators\n- Uncertainty acknowledgment' : ''}
1370 | 
1371 | Please analyze the available data and provide a comprehensive answer to: "${question}"`
1372 |             }
1373 |           }
1374 |         ];
1375 |         break;
1376 | 
1377 |       case 'smart_data_transformation':
1378 |         const { source_table, transformation_goal, target_format, quality_rules, preserve_history = 'true' } = promptArgs;
1379 |         messages = [
1380 |           {
1381 |             role: 'user',
1382 |             content: {
1383 |               type: 'text',
1384 |               text: `🔄 INTELLIGENT DATA TRANSFORMATION
1385 | 
1386 | **Source Table**: ${source_table}
1387 | **Transformation Goal**: ${transformation_goal}
1388 | ${target_format ? `**Target Format**: ${target_format}` : ''}
1389 | ${quality_rules ? `**Quality Rules**: ${quality_rules}` : ''}
1390 | **Preserve History**: ${preserve_history}
1391 | 
1392 | **Transformation Framework**:
1393 | 1. Source data analysis and quality assessment
1394 | 2. Transformation strategy development
1395 | 3. Data mapping and conversion rules
1396 | 4. Quality validation and error handling
1397 | 5. Output optimization and validation
1398 | 
1399 | **${transformation_goal.toUpperCase()} PROCESS**:
1400 | ${transformation_goal === 'normalize' ? '- Database normalization principles\n- Redundancy elimination\n- Relationship optimization' : ''}
1401 | ${transformation_goal === 'standardize' ? '- Format standardization\n- Value normalization\n- Consistency enforcement' : ''}
1402 | ${transformation_goal === 'enrich' ? '- Data augmentation strategies\n- External data integration\n- Value-added field creation' : ''}
1403 | ${transformation_goal === 'cleanse' ? '- Data validation and correction\n- Duplicate removal\n- Missing value handling' : ''}
1404 | 
1405 | **Deliverables**:
1406 | - Transformation execution plan
1407 | - Data mapping specifications
1408 | - Quality validation results
1409 | - Performance optimization recommendations
1410 | ${preserve_history === 'true' ? '- Change audit trail and versioning' : ''}
1411 | - Post-transformation validation
1412 | 
1413 | Please analyze the source data and execute ${transformation_goal} transformation with intelligent optimization.`
1414 |             }
1415 |           }
1416 |         ];
1417 |         break;
1418 | 
1419 |       case 'automation_recommendations':
1420 |         const { workflow_description, automation_scope = 'single_table', frequency_patterns, complexity_tolerance = 'moderate', integration_capabilities } = promptArgs;
1421 |         messages = [
1422 |           {
1423 |             role: 'user',
1424 |             content: {
1425 |               type: 'text',
1426 |               text: `🤖 INTELLIGENT AUTOMATION RECOMMENDATIONS
1427 | 
1428 | **Automation Scope**: ${automation_scope}
1429 | **Complexity Tolerance**: ${complexity_tolerance}
1430 | ${workflow_description ? `**Current Workflow**: ${workflow_description}` : ''}
1431 | ${frequency_patterns ? `**Frequency Patterns**: ${frequency_patterns}` : ''}
1432 | ${integration_capabilities ? `**Integration Tools**: ${integration_capabilities}` : ''}
1433 | 
1434 | **Automation Analysis Framework**:
1435 | 1. Workflow pattern analysis and task identification
1436 | 2. Automation opportunity assessment and prioritization
1437 | 3. Technical feasibility and complexity evaluation
1438 | 4. ROI calculation and benefit quantification
1439 | 5. Implementation roadmap development
1440 | 
1441 | **${automation_scope.toUpperCase()} AUTOMATION**:
1442 | ${automation_scope === 'single_table' ? '- Field auto-population rules\n- Data validation automation\n- Notification triggers' : ''}
1443 | ${automation_scope === 'multi_table' ? '- Cross-table data synchronization\n- Workflow orchestration\n- Complex business logic automation' : ''}
1444 | ${automation_scope === 'external_integration' ? '- API integration strategies\n- Data pipeline automation\n- Third-party tool connectivity' : ''}
1445 | 
1446 | **Recommendations**:
1447 | - High-impact automation opportunities
1448 | - Implementation complexity assessment
1449 | - Cost-benefit analysis with ROI projections
1450 | - Technical requirements and dependencies
1451 | - Risk assessment and mitigation strategies
1452 | - Success metrics and monitoring approach
1453 | 
1454 | Please analyze the workflow patterns and provide ${complexity_tolerance}-level automation recommendations for ${automation_scope} scope.`
1455 |             }
1456 |           }
1457 |         ];
1458 |         break;
1459 |         
1460 |       default:
1461 |         throw new Error(`Unsupported prompt: ${promptName}`);
1462 |     }
1463 |     
1464 |     return {
1465 |       jsonrpc: '2.0',
1466 |       id: request.id,
1467 |       result: {
1468 |         description: prompt.description,
1469 |         messages: messages
1470 |       }
1471 |     };
1472 |     
1473 |   } catch (error) {
1474 |     log(LOG_LEVELS.ERROR, `Prompt ${promptName} failed`, { error: error.message });
1475 |     
1476 |     return {
1477 |       jsonrpc: '2.0',
1478 |       id: request.id,
1479 |       error: {
1480 |         code: -32000,
1481 |         message: `Error getting prompt ${promptName}: ${error.message}`
1482 |       }
1483 |     };
1484 |   }
1485 | }
1486 | 
1487 | // Sampling handler
1488 | async function handleSampling(request) {
1489 |   const { messages, modelPreferences } = request.params;
1490 |   
1491 |   try {
1492 |     // Note: In a real implementation, this would integrate with an LLM API
1493 |     // For now, we'll return a structured response indicating sampling capability
1494 |     
1495 |     log(LOG_LEVELS.INFO, 'Sampling request received', { 
1496 |       messageCount: messages?.length,
1497 |       model: modelPreferences?.model 
1498 |     });
1499 |     
1500 |     return {
1501 |       jsonrpc: '2.0',
1502 |       id: request.id,
1503 |       result: {
1504 |         model: modelPreferences?.model || 'claude-3-sonnet',
1505 |         role: 'assistant',
1506 |         content: {
1507 |           type: 'text',
1508 |           text: 'Sampling capability is available. This MCP server can request AI assistance for complex data analysis and insights generation. In a full implementation, this would connect to your preferred LLM for intelligent Airtable operations.'
1509 |         },
1510 |         stopReason: 'end_turn'
1511 |       }
1512 |     };
1513 |     
1514 |   } catch (error) {
1515 |     log(LOG_LEVELS.ERROR, 'Sampling failed', { error: error.message });
1516 |     
1517 |     return {
1518 |       jsonrpc: '2.0',
1519 |       id: request.id,
1520 |       error: {
1521 |         code: -32000,
1522 |         message: `Sampling error: ${error.message}`
1523 |       }
1524 |     };
1525 |   }
1526 | }
1527 | 
1528 | // Server startup
1529 | const PORT = CONFIG.PORT;
1530 | const HOST = CONFIG.HOST;
1531 | 
1532 | server.listen(PORT, HOST, () => {
1533 |   log(LOG_LEVELS.INFO, `Airtable MCP Server started`, {
1534 |     host: HOST,
1535 |     port: PORT,
1536 |     version: '2.1.0'
1537 |   });
1538 |   
1539 |   console.log(`
1540 | ╔═══════════════════════════════════════════════════════════════╗
1541 | ║                 Airtable MCP Server v2.1                     ║
1542 | ║            Model Context Protocol Implementation              ║
1543 | ╠═══════════════════════════════════════════════════════════════╣
1544 | ║  🌐 MCP Endpoint: http://${HOST}:${PORT}/mcp                  ║
1545 | ║  📊 Health Check: http://${HOST}:${PORT}/health               ║
1546 | ║  🔒 Security: Rate limiting, input validation                ║
1547 | ║  📋 Tools: ${TOOLS_SCHEMA.length} available operations                    ║
1548 | ╠═══════════════════════════════════════════════════════════════╣
1549 | ║  🔗 Connected to Airtable Base: ${baseId.slice(0, 8)}...        ║
1550 | ║  🚀 Ready for MCP client connections                         ║
1551 | ╚═══════════════════════════════════════════════════════════════╝
1552 |   `);
1553 | });
1554 | 
1555 | // Graceful shutdown
1556 | function gracefulShutdown(signal) {
1557 |   log(LOG_LEVELS.INFO, 'Graceful shutdown initiated', { signal });
1558 |   
1559 |   server.close(() => {
1560 |     log(LOG_LEVELS.INFO, 'Server stopped');
1561 |     process.exit(0);
1562 |   });
1563 |   
1564 |   setTimeout(() => {
1565 |     log(LOG_LEVELS.ERROR, 'Force shutdown - server did not close in time');
1566 |     process.exit(1);
1567 |   }, 10000);
1568 | }
1569 | 
1570 | process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
1571 | process.on('SIGINT', () => gracefulShutdown('SIGINT'));
1572 | 
1573 | process.on('uncaughtException', (error) => {
1574 |   log(LOG_LEVELS.ERROR, 'Uncaught exception', { error: error.message });
1575 |   gracefulShutdown('uncaughtException');
1576 | });
1577 | 
1578 | process.on('unhandledRejection', (reason) => {
1579 |   log(LOG_LEVELS.ERROR, 'Unhandled promise rejection', { reason: reason?.toString() });
1580 |   gracefulShutdown('unhandledRejection');
1581 | });
```

--------------------------------------------------------------------------------
/src/javascript/airtable_simple.js:
--------------------------------------------------------------------------------

```javascript
   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 | });
```
Page 4/5FirstPrevNextLast