#
tokens: 49108/50000 23/119 files (page 2/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 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/python/inspector.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | MCP Tool Inspector
  4 | -----------------
  5 | A simple script to list tools in a format Smithery can understand
  6 | """
  7 | import json
  8 | 
  9 | # Define the tools manually
 10 | tools = [
 11 |     {
 12 |         "name": "list_bases",
 13 |         "description": "List all accessible Airtable bases",
 14 |         "parameters": {
 15 |             "type": "object",
 16 |             "properties": {},
 17 |             "required": []
 18 |         },
 19 |         "returns": {
 20 |             "type": "string"
 21 |         }
 22 |     },
 23 |     {
 24 |         "name": "list_tables",
 25 |         "description": "List all tables in the specified base or the default base",
 26 |         "parameters": {
 27 |             "type": "object",
 28 |             "properties": {
 29 |                 "base_id": {
 30 |                     "type": "string",
 31 |                     "description": "Optional base ID to use instead of the default"
 32 |                 }
 33 |             },
 34 |             "required": []
 35 |         },
 36 |         "returns": {
 37 |             "type": "string"
 38 |         }
 39 |     },
 40 |     {
 41 |         "name": "list_records",
 42 |         "description": "List records from a table with optional filtering",
 43 |         "parameters": {
 44 |             "type": "object",
 45 |             "properties": {
 46 |                 "table_name": {
 47 |                     "type": "string",
 48 |                     "description": "Name of the table to list records from"
 49 |                 },
 50 |                 "max_records": {
 51 |                     "type": "integer",
 52 |                     "description": "Maximum number of records to return (default: 100)"
 53 |                 },
 54 |                 "filter_formula": {
 55 |                     "type": "string",
 56 |                     "description": "Optional Airtable formula to filter records"
 57 |                 }
 58 |             },
 59 |             "required": ["table_name"]
 60 |         },
 61 |         "returns": {
 62 |             "type": "string"
 63 |         }
 64 |     },
 65 |     {
 66 |         "name": "get_record",
 67 |         "description": "Get a specific record from a table",
 68 |         "parameters": {
 69 |             "type": "object",
 70 |             "properties": {
 71 |                 "table_name": {
 72 |                     "type": "string",
 73 |                     "description": "Name of the table"
 74 |                 },
 75 |                 "record_id": {
 76 |                     "type": "string",
 77 |                     "description": "ID of the record to retrieve"
 78 |                 }
 79 |             },
 80 |             "required": ["table_name", "record_id"]
 81 |         },
 82 |         "returns": {
 83 |             "type": "string"
 84 |         }
 85 |     },
 86 |     {
 87 |         "name": "create_records",
 88 |         "description": "Create records in a table from JSON string",
 89 |         "parameters": {
 90 |             "type": "object",
 91 |             "properties": {
 92 |                 "table_name": {
 93 |                     "type": "string",
 94 |                     "description": "Name of the table"
 95 |                 },
 96 |                 "records_json": {
 97 |                     "type": "string",
 98 |                     "description": "JSON string containing the records to create"
 99 |                 }
100 |             },
101 |             "required": ["table_name", "records_json"]
102 |         },
103 |         "returns": {
104 |             "type": "string"
105 |         }
106 |     },
107 |     {
108 |         "name": "update_records",
109 |         "description": "Update records in a table from JSON string",
110 |         "parameters": {
111 |             "type": "object",
112 |             "properties": {
113 |                 "table_name": {
114 |                     "type": "string",
115 |                     "description": "Name of the table"
116 |                 },
117 |                 "records_json": {
118 |                     "type": "string",
119 |                     "description": "JSON string containing the records to update with IDs"
120 |                 }
121 |             },
122 |             "required": ["table_name", "records_json"]
123 |         },
124 |         "returns": {
125 |             "type": "string"
126 |         }
127 |     },
128 |     {
129 |         "name": "set_base_id",
130 |         "description": "Set the current Airtable base ID",
131 |         "parameters": {
132 |             "type": "object",
133 |             "properties": {
134 |                 "base_id": {
135 |                     "type": "string",
136 |                     "description": "Base ID to set as the current base"
137 |                 }
138 |             },
139 |             "required": ["base_id"]
140 |         },
141 |         "returns": {
142 |             "type": "string"
143 |         }
144 |     }
145 | ]
146 | 
147 | # Print the tools as JSON
148 | print(json.dumps({"tools": tools}, indent=2)) 
```

--------------------------------------------------------------------------------
/src/typescript/prompt-templates.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Runtime AI prompt templates for Airtable MCP Server
 3 |  */
 4 | 
 5 | import type { PromptSchema } from './index';
 6 | 
 7 | export const AI_PROMPT_TEMPLATES: Record<string, PromptSchema> = {
 8 |   analyze_data: {
 9 |     name: 'analyze_data',
10 |     description: 'Advanced AI data analysis with statistical insights, pattern recognition, and predictive modeling',
11 |     arguments: [
12 |       { name: 'table', description: 'Table name or ID to analyze', required: true, type: 'string' },
13 |       { name: 'analysis_type', description: 'Type of analysis', required: false, type: 'string', enum: ['trends', 'statistical', 'patterns', 'predictive', 'anomaly_detection', 'correlation_matrix'] },
14 |       { name: 'field_focus', description: 'Specific fields to focus the analysis on', required: false, type: 'string' },
15 |       { name: 'time_dimension', description: 'Time field for temporal analysis', required: false, type: 'string' },
16 |       { name: 'confidence_level', description: 'Statistical confidence level', required: false, type: 'number', enum: ['0.90', '0.95', '0.99'] }
17 |     ]
18 |   },
19 |   
20 |   create_report: {
21 |     name: 'create_report',
22 |     description: 'Intelligent report generation with business insights and stakeholder-specific recommendations',
23 |     arguments: [
24 |       { name: 'table', description: 'Table name or ID for report data', required: true, type: 'string' },
25 |       { name: 'report_type', description: 'Type of report to generate', required: true, type: 'string', enum: ['executive_summary', 'detailed_analysis', 'dashboard', 'stakeholder_report'] },
26 |       { name: 'target_audience', description: 'Primary audience for the report', required: true, type: 'string', enum: ['executives', 'managers', 'analysts', 'technical_team'] },
27 |       { name: 'include_recommendations', description: 'Include actionable recommendations', required: false, type: 'boolean' },
28 |       { name: 'time_period', description: 'Time period for analysis', required: false, type: 'string' },
29 |       { name: 'format_preference', description: 'Preferred report format', required: false, type: 'string', enum: ['narrative', 'bullet_points', 'charts', 'mixed'] }
30 |     ]
31 |   },
32 |   
33 |   predictive_analytics: {
34 |     name: 'predictive_analytics',
35 |     description: 'Advanced forecasting and trend prediction with multiple algorithms and uncertainty quantification',
36 |     arguments: [
37 |       { name: 'table', description: 'Table name or ID for prediction', required: true, type: 'string' },
38 |       { name: 'target_field', description: 'Field to predict', required: true, type: 'string' },
39 |       { name: 'prediction_periods', description: 'Number of periods to predict', required: false, type: 'number' },
40 |       { name: 'algorithm', description: 'Prediction algorithm to use', required: false, type: 'string', enum: ['linear_regression', 'arima', 'exponential_smoothing', 'random_forest', 'neural_network'] },
41 |       { name: 'include_confidence_intervals', description: 'Include confidence intervals', required: false, type: 'boolean' },
42 |       { name: 'historical_periods', description: 'Historical periods for training', required: false, type: 'number' },
43 |       { name: 'external_factors', description: 'External factors to consider', required: false, type: 'string' },
44 |       { name: 'business_context', description: 'Business context for predictions', required: false, type: 'string' }
45 |     ]
46 |   },
47 |   
48 |   natural_language_query: {
49 |     name: 'natural_language_query',
50 |     description: 'Process natural language questions about data with intelligent context awareness',
51 |     arguments: [
52 |       { name: 'question', description: 'Natural language question about the data', required: true, type: 'string' },
53 |       { name: 'tables', description: 'Specific tables to search (optional)', required: false, type: 'string' },
54 |       { name: 'response_format', description: 'Preferred response format', required: false, type: 'string', enum: ['natural_language', 'structured_data', 'visualization_ready', 'action_items'] },
55 |       { name: 'context_awareness', description: 'Use context from previous queries', required: false, type: 'boolean' },
56 |       { name: 'confidence_threshold', description: 'Minimum confidence for responses', required: false, type: 'number' },
57 |       { name: 'clarifying_questions', description: 'Ask clarifying questions if needed', required: false, type: 'boolean' }
58 |     ]
59 |   }
60 | };
```

--------------------------------------------------------------------------------
/examples/typescript/basic-usage.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Basic TypeScript Usage Example
  3 |  * Demonstrates type-safe Airtable MCP operations
  4 |  */
  5 | 
  6 | import {
  7 |   AirtableMCPServer,
  8 |   MCPServerCapabilities,
  9 |   ListRecordsInput,
 10 |   CreateRecordInput,
 11 |   AnalyzeDataPrompt
 12 | } from '@rashidazarang/airtable-mcp/types';
 13 | 
 14 | // Type-safe server initialization
 15 | async function initializeServer(): Promise<void> {
 16 |   const server = new AirtableMCPServer();
 17 |   
 18 |   const capabilities: MCPServerCapabilities = {
 19 |     tools: { listChanged: false },
 20 |     prompts: { listChanged: false },
 21 |     resources: { subscribe: false, listChanged: false },
 22 |     roots: { listChanged: false },
 23 |     sampling: {},
 24 |     logging: {}
 25 |   };
 26 |   
 27 |   const serverInfo = await server.initialize(capabilities);
 28 |   console.log('Server initialized:', serverInfo);
 29 | }
 30 | 
 31 | // Type-safe data operations
 32 | async function performDataOperations(): Promise<void> {
 33 |   const server = new AirtableMCPServer();
 34 |   
 35 |   // List records with type safety
 36 |   const listParams: ListRecordsInput = {
 37 |     table: 'Tasks',
 38 |     maxRecords: 10,
 39 |     filterByFormula: "Status = 'Active'"
 40 |   };
 41 |   
 42 |   const records = await server.handleToolCall('list_records', listParams);
 43 |   console.log('Records retrieved:', records);
 44 |   
 45 |   // Create record with validated types
 46 |   const createParams: CreateRecordInput = {
 47 |     table: 'Tasks',
 48 |     fields: {
 49 |       'Name': 'New Task',
 50 |       'Status': 'Active',
 51 |       'Priority': 'High',
 52 |       'Due Date': new Date().toISOString()
 53 |     },
 54 |     typecast: true
 55 |   };
 56 |   
 57 |   const newRecord = await server.handleToolCall('create_record', createParams);
 58 |   console.log('Record created:', newRecord);
 59 | }
 60 | 
 61 | // Type-safe AI prompt usage
 62 | async function useAIPrompts(): Promise<void> {
 63 |   const server = new AirtableMCPServer();
 64 |   
 65 |   // Advanced data analysis with strict typing
 66 |   const analysisParams: AnalyzeDataPrompt = {
 67 |     table: 'Sales',
 68 |     analysis_type: 'predictive',
 69 |     field_focus: 'revenue,conversion_rate',
 70 |     time_dimension: 'created_date',
 71 |     confidence_level: 0.95
 72 |   };
 73 |   
 74 |   const analysis = await server.handlePromptGet('analyze_data', analysisParams);
 75 |   console.log('AI Analysis:', analysis);
 76 |   
 77 |   // Type-safe error handling
 78 |   try {
 79 |     // This will cause a TypeScript compile error if types don't match
 80 |     const invalidParams = {
 81 |       table: 'Sales',
 82 |       analysis_type: 'invalid_type', // TypeScript will catch this!
 83 |       confidence_level: 1.5 // TypeScript will catch this too!
 84 |     };
 85 |     
 86 |     // await server.handlePromptGet('analyze_data', invalidParams);
 87 |   } catch (error) {
 88 |     console.error('Type-safe error handling:', error);
 89 |   }
 90 | }
 91 | 
 92 | // Enterprise-grade type validation
 93 | interface BusinessMetrics {
 94 |   revenue: number;
 95 |   conversion_rate: number;
 96 |   customer_count: number;
 97 |   timestamp: Date;
 98 | }
 99 | 
100 | function validateBusinessMetrics(data: unknown): data is BusinessMetrics {
101 |   const metrics = data as BusinessMetrics;
102 |   return (
103 |     typeof metrics.revenue === 'number' &&
104 |     typeof metrics.conversion_rate === 'number' &&
105 |     typeof metrics.customer_count === 'number' &&
106 |     metrics.timestamp instanceof Date
107 |   );
108 | }
109 | 
110 | // Type-safe configuration
111 | interface AppConfig {
112 |   airtable: {
113 |     token: string;
114 |     baseId: string;
115 |   };
116 |   server: {
117 |     port: number;
118 |     host: string;
119 |     logLevel: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
120 |   };
121 |   ai: {
122 |     enablePredictiveAnalytics: boolean;
123 |     confidenceThreshold: number;
124 |     maxAnalysisFields: number;
125 |   };
126 | }
127 | 
128 | const config: AppConfig = {
129 |   airtable: {
130 |     token: process.env.AIRTABLE_TOKEN!,
131 |     baseId: process.env.AIRTABLE_BASE_ID!
132 |   },
133 |   server: {
134 |     port: 8010,
135 |     host: 'localhost',
136 |     logLevel: 'INFO'
137 |   },
138 |   ai: {
139 |     enablePredictiveAnalytics: true,
140 |     confidenceThreshold: 0.85,
141 |     maxAnalysisFields: 10
142 |   }
143 | };
144 | 
145 | // Main execution with comprehensive error handling
146 | async function main(): Promise<void> {
147 |   try {
148 |     console.log('🚀 Starting TypeScript Airtable MCP Example');
149 |     
150 |     await initializeServer();
151 |     await performDataOperations();
152 |     await useAIPrompts();
153 |     
154 |     console.log('✅ All operations completed successfully with type safety!');
155 |   } catch (error) {
156 |     console.error('❌ Error occurred:', error);
157 |     process.exit(1);
158 |   }
159 | }
160 | 
161 | // Export for testing and reuse
162 | export {
163 |   initializeServer,
164 |   performDataOperations,
165 |   useAIPrompts,
166 |   validateBusinessMetrics,
167 |   BusinessMetrics,
168 |   AppConfig
169 | };
170 | 
171 | // Run if this file is executed directly
172 | if (require.main === module) {
173 |   main();
174 | }
```

--------------------------------------------------------------------------------
/src/typescript/app/tools/query.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createHash } from 'node:crypto';
  2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
  3 | import {
  4 |   QueryInput,
  5 |   QueryOutput,
  6 |   queryInputSchema,
  7 |   queryInputShape,
  8 |   queryOutputSchema
  9 | } from '../types';
 10 | import { AppContext } from '../context';
 11 | import { handleToolError } from './handleError';
 12 | 
 13 | type PiiPolicy = {
 14 |   field: string;
 15 |   policy: 'mask' | 'hash' | 'drop';
 16 | };
 17 | 
 18 | function maskValue(value: unknown): unknown {
 19 |   if (value === null || value === undefined) {
 20 |     return value;
 21 |   }
 22 |   if (Array.isArray(value)) {
 23 |     return value.map(() => '••••');
 24 |   }
 25 |   if (typeof value === 'object') {
 26 |     return '[redacted]';
 27 |   }
 28 |   return '••••';
 29 | }
 30 | 
 31 | function hashValue(value: unknown): string {
 32 |   const serialized =
 33 |     typeof value === 'string' ? value : JSON.stringify(value ?? '');
 34 |   return createHash('sha256').update(serialized).digest('hex');
 35 | }
 36 | 
 37 | function applyPiiPolicies(
 38 |   fields: Record<string, unknown>,
 39 |   policies: PiiPolicy[]
 40 | ): Record<string, unknown> {
 41 |   if (!policies.length) {
 42 |     return fields;
 43 |   }
 44 |   const result: Record<string, unknown> = { ...fields };
 45 |   for (const policy of policies) {
 46 |     if (!(policy.field in result)) continue;
 47 |     switch (policy.policy) {
 48 |       case 'drop':
 49 |         delete result[policy.field];
 50 |         break;
 51 |       case 'mask':
 52 |         result[policy.field] = maskValue(result[policy.field]);
 53 |         break;
 54 |       case 'hash':
 55 |         result[policy.field] = hashValue(result[policy.field]);
 56 |         break;
 57 |       default:
 58 |         break;
 59 |     }
 60 |   }
 61 |   return result;
 62 | }
 63 | 
 64 | export function registerQueryTool(server: McpServer, ctx: AppContext): void {
 65 |   server.registerTool(
 66 |     'query',
 67 |     {
 68 |       description: 'Query Airtable records with filtering, sorting, and pagination.',
 69 |       inputSchema: queryInputShape,
 70 |       outputSchema: queryOutputSchema.shape
 71 |     },
 72 |     async (args: QueryInput, _extra: unknown) => {
 73 |       try {
 74 |         const input = queryInputSchema.parse(args);
 75 |         ctx.governance.ensureOperationAllowed('query');
 76 |         ctx.governance.ensureBaseAllowed(input.baseId);
 77 |         ctx.governance.ensureTableAllowed(input.baseId, input.table);
 78 | 
 79 |         const logger = ctx.logger.child({
 80 |           tool: 'query',
 81 |           baseId: input.baseId,
 82 |           table: input.table
 83 |         });
 84 | 
 85 |         const queryParams: Record<string, string | number | boolean | Array<string>> = {};
 86 | 
 87 |         if (input.fields) {
 88 |           queryParams.fields = input.fields;
 89 |         }
 90 |         if (input.filterByFormula) {
 91 |           queryParams.filterByFormula = input.filterByFormula;
 92 |         }
 93 |         if (input.view) {
 94 |           queryParams.view = input.view;
 95 |         }
 96 |         if (input.pageSize) {
 97 |           queryParams.pageSize = input.pageSize;
 98 |         }
 99 |         if (input.maxRecords) {
100 |           queryParams.maxRecords = input.maxRecords;
101 |         }
102 |         if (input.offset) {
103 |           queryParams.offset = input.offset;
104 |         }
105 |         if (typeof input.returnFieldsByFieldId === 'boolean') {
106 |           queryParams.returnFieldsByFieldId = input.returnFieldsByFieldId;
107 |         }
108 | 
109 |         if (input.sorts) {
110 |           input.sorts.forEach((sort, index) => {
111 |             queryParams[`sort[${index}][field]`] = sort.field;
112 |             queryParams[`sort[${index}][direction]`] = sort.direction ?? 'asc';
113 |           });
114 |         }
115 | 
116 |         const response: any = await ctx.airtable.queryRecords(input.baseId, input.table, queryParams);
117 |         const rawRecords: Array<Record<string, unknown>> = Array.isArray(response?.records)
118 |           ? response.records
119 |           : [];
120 | 
121 |         const piiPolicies = ctx.governance.listPiiPolicies(input.baseId, input.table) as PiiPolicy[];
122 | 
123 |         const sanitizedRecords = rawRecords.map((record) => {
124 |           const fields = typeof record.fields === 'object' && record.fields !== null ? record.fields : {};
125 |           return {
126 |             id: String(record.id ?? ''),
127 |             createdTime: record.createdTime ? String(record.createdTime) : undefined,
128 |             fields: applyPiiPolicies(fields as Record<string, unknown>, piiPolicies)
129 |           };
130 |         });
131 | 
132 |         const structuredContent: QueryOutput = {
133 |           records: sanitizedRecords,
134 |           offset: typeof response?.offset === 'string' ? response.offset : undefined,
135 |           summary: {
136 |             returned: sanitizedRecords.length,
137 |             hasMore: Boolean(response?.offset)
138 |           }
139 |         };
140 | 
141 |         logger.debug('Query completed', {
142 |           returned: sanitizedRecords.length,
143 |           hasMore: structuredContent.summary?.hasMore
144 |         });
145 | 
146 |         return {
147 |           structuredContent,
148 |           content: [] as const
149 |         };
150 |       } catch (error) {
151 |         return handleToolError('query', error, ctx);
152 |       }
153 |     }
154 |   );
155 | }
156 | 
```

--------------------------------------------------------------------------------
/src/python/simple_airtable_server.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Simple Airtable MCP Server for Claude
  4 | -------------------------------------
  5 | A minimal MCP server that implements Airtable tools and Claude's special methods
  6 | """
  7 | import os
  8 | import sys
  9 | import json
 10 | import logging
 11 | import requests
 12 | import traceback
 13 | from typing import Dict, Any, List, Optional
 14 | 
 15 | # Check if MCP SDK is installed
 16 | try:
 17 |     from mcp.server.fastmcp import FastMCP
 18 | except ImportError:
 19 |     print("Error: MCP SDK not found. Please install with 'pip install mcp'")
 20 |     sys.exit(1)
 21 | 
 22 | # Parse command line arguments
 23 | if len(sys.argv) < 5:
 24 |     print("Usage: python3 simple_airtable_server.py --token YOUR_TOKEN --base YOUR_BASE_ID")
 25 |     sys.exit(1)
 26 | 
 27 | # Get the token and base ID from command line arguments
 28 | token = None
 29 | base_id = None
 30 | for i in range(1, len(sys.argv)):
 31 |     if sys.argv[i] == "--token" and i+1 < len(sys.argv):
 32 |         token = sys.argv[i+1]
 33 |     elif sys.argv[i] == "--base" and i+1 < len(sys.argv):
 34 |         base_id = sys.argv[i+1]
 35 | 
 36 | if not token:
 37 |     print("Error: No Airtable token provided. Use --token parameter.")
 38 |     sys.exit(1)
 39 | 
 40 | if not base_id:
 41 |     print("Error: No base ID provided. Use --base parameter.")
 42 |     sys.exit(1)
 43 | 
 44 | # Set up logging
 45 | logging.basicConfig(level=logging.INFO)
 46 | logger = logging.getLogger("airtable-mcp")
 47 | 
 48 | # Create MCP server
 49 | app = FastMCP("Airtable Tools")
 50 | 
 51 | # Helper function for Airtable API calls
 52 | async def airtable_api_call(endpoint, method="GET", data=None, params=None):
 53 |     """Make an Airtable API call with error handling"""
 54 |     headers = {
 55 |         "Authorization": f"Bearer {token}",
 56 |         "Content-Type": "application/json"
 57 |     }
 58 |     
 59 |     url = f"https://api.airtable.com/v0/{endpoint}"
 60 |     
 61 |     try:
 62 |         if method == "GET":
 63 |             response = requests.get(url, headers=headers, params=params)
 64 |         elif method == "POST":
 65 |             response = requests.post(url, headers=headers, json=data)
 66 |         else:
 67 |             raise ValueError(f"Unsupported method: {method}")
 68 |         
 69 |         response.raise_for_status()
 70 |         return response.json()
 71 |     except Exception as e:
 72 |         logger.error(f"API call error: {str(e)}")
 73 |         return {"error": str(e)}
 74 | 
 75 | # Claude-specific methods
 76 | @app.rpc_method("resources/list")
 77 | async def resources_list(params: Dict = None) -> Dict:
 78 |     """List available Airtable resources for Claude"""
 79 |     try:
 80 |         # Return a simple list of resources
 81 |         resources = [
 82 |             {"id": "airtable_tables", "name": "Airtable Tables", "description": "Tables in your Airtable base"}
 83 |         ]
 84 |         return {"resources": resources}
 85 |     except Exception as e:
 86 |         logger.error(f"Error in resources/list: {str(e)}")
 87 |         return {"error": {"code": -32000, "message": str(e)}}
 88 | 
 89 | @app.rpc_method("prompts/list")
 90 | async def prompts_list(params: Dict = None) -> Dict:
 91 |     """List available prompts for Claude"""
 92 |     try:
 93 |         # Return a simple list of prompts
 94 |         prompts = [
 95 |             {"id": "tables_prompt", "name": "List Tables", "description": "List all tables"}
 96 |         ]
 97 |         return {"prompts": prompts}
 98 |     except Exception as e:
 99 |         logger.error(f"Error in prompts/list: {str(e)}")
100 |         return {"error": {"code": -32000, "message": str(e)}}
101 | 
102 | # Airtable tool functions
103 | @app.tool()
104 | async def list_tables() -> str:
105 |     """List all tables in the specified base"""
106 |     try:
107 |         result = await airtable_api_call(f"meta/bases/{base_id}/tables")
108 |         
109 |         if "error" in result:
110 |             return f"Error: {result['error']}"
111 |         
112 |         tables = result.get("tables", [])
113 |         if not tables:
114 |             return "No tables found in this base."
115 |         
116 |         table_list = [f"{i+1}. {table['name']} (ID: {table['id']})" 
117 |                     for i, table in enumerate(tables)]
118 |         return "Tables in this base:\n" + "\n".join(table_list)
119 |     except Exception as e:
120 |         return f"Error listing tables: {str(e)}"
121 | 
122 | @app.tool()
123 | async def list_records(table_name: str, max_records: int = 100) -> str:
124 |     """List records from a table"""
125 |     try:
126 |         params = {"maxRecords": max_records}
127 |         result = await airtable_api_call(f"{base_id}/{table_name}", params=params)
128 |         
129 |         if "error" in result:
130 |             return f"Error: {result['error']}"
131 |         
132 |         records = result.get("records", [])
133 |         if not records:
134 |             return "No records found in this table."
135 |         
136 |         # Format the records for display
137 |         formatted_records = []
138 |         for i, record in enumerate(records):
139 |             record_id = record.get("id", "unknown")
140 |             fields = record.get("fields", {})
141 |             field_text = ", ".join([f"{k}: {v}" for k, v in fields.items()])
142 |             formatted_records.append(f"{i+1}. ID: {record_id} - {field_text}")
143 |         
144 |         return "Records:\n" + "\n".join(formatted_records)
145 |     except Exception as e:
146 |         return f"Error listing records: {str(e)}"
147 | 
148 | # Start the server
149 | if __name__ == "__main__":
150 |     print(f"Starting Airtable MCP Server with token {token[:5]}...{token[-5:]} and base {base_id}")
151 |     app.start() 
```

--------------------------------------------------------------------------------
/tests/test_mcp_comprehensive.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Comprehensive Test Script for Airtable MCP
  5 |  * Tests all available MCP tools and functionality
  6 |  */
  7 | 
  8 | const http = require('http');
  9 | 
 10 | const MCP_SERVER_URL = 'http://localhost:8010/mcp';
 11 | const TEST_TOKEN = process.env.AIRTABLE_TOKEN || 'YOUR_AIRTABLE_TOKEN_HERE';
 12 | const TEST_BASE_ID = process.env.AIRTABLE_BASE_ID || 'YOUR_BASE_ID_HERE';
 13 | 
 14 | if (TEST_TOKEN === 'YOUR_AIRTABLE_TOKEN_HERE' || TEST_BASE_ID === 'YOUR_BASE_ID_HERE') {
 15 |   console.error('Error: Please set AIRTABLE_TOKEN and AIRTABLE_BASE_ID environment variables');
 16 |   console.error('Example: export AIRTABLE_TOKEN=your_token_here');
 17 |   console.error('         export AIRTABLE_BASE_ID=your_base_id_here');
 18 |   process.exit(1);
 19 | }
 20 | 
 21 | // Helper function to make MCP requests
 22 | function makeMCPRequest(method, params = {}) {
 23 |   return new Promise((resolve, reject) => {
 24 |     const postData = JSON.stringify({
 25 |       jsonrpc: '2.0',
 26 |       id: Date.now(),
 27 |       method: method,
 28 |       params: params
 29 |     });
 30 | 
 31 |     const options = {
 32 |       hostname: 'localhost',
 33 |       port: 8010,
 34 |       path: '/mcp',
 35 |       method: 'POST',
 36 |       headers: {
 37 |         'Content-Type': 'application/json',
 38 |         'Content-Length': Buffer.byteLength(postData)
 39 |       }
 40 |     };
 41 | 
 42 |     const req = http.request(options, (res) => {
 43 |       let data = '';
 44 |       res.on('data', (chunk) => {
 45 |         data += chunk;
 46 |       });
 47 |       res.on('end', () => {
 48 |         try {
 49 |           const response = JSON.parse(data);
 50 |           resolve(response);
 51 |         } catch (e) {
 52 |           reject(new Error(`Failed to parse response: ${e.message}`));
 53 |         }
 54 |       });
 55 |     });
 56 | 
 57 |     req.on('error', (e) => {
 58 |       reject(new Error(`Request failed: ${e.message}`));
 59 |     });
 60 | 
 61 |     req.write(postData);
 62 |     req.end();
 63 |   });
 64 | }
 65 | 
 66 | async function runComprehensiveTest() {
 67 |   console.log('🔌 Airtable MCP Comprehensive Test');
 68 |   console.log('===================================');
 69 |   console.log(`Server: ${MCP_SERVER_URL}`);
 70 |   console.log(`Base ID: ${TEST_BASE_ID}`);
 71 |   console.log(`Token: ${TEST_TOKEN.substring(0, 10)}...${TEST_TOKEN.substring(TEST_TOKEN.length - 10)}`);
 72 |   console.log('');
 73 | 
 74 |   try {
 75 |     // Test 1: List Resources
 76 |     console.log('📋 Test 1: Listing Resources');
 77 |     console.log('----------------------------');
 78 |     const resourcesResponse = await makeMCPRequest('resources/list');
 79 |     console.log('✅ Resources Response:');
 80 |     console.log(JSON.stringify(resourcesResponse, null, 2));
 81 |     console.log('');
 82 | 
 83 |     // Test 2: List Prompts
 84 |     console.log('📝 Test 2: Listing Prompts');
 85 |     console.log('-------------------------');
 86 |     const promptsResponse = await makeMCPRequest('prompts/list');
 87 |     console.log('✅ Prompts Response:');
 88 |     console.log(JSON.stringify(promptsResponse, null, 2));
 89 |     console.log('');
 90 | 
 91 |     // Test 3: List Tables
 92 |     console.log('📊 Test 3: Listing Tables');
 93 |     console.log('------------------------');
 94 |     const tablesResponse = await makeMCPRequest('tools/call', {
 95 |       name: 'list_tables'
 96 |     });
 97 |     console.log('✅ Tables Response:');
 98 |     console.log(JSON.stringify(tablesResponse, null, 2));
 99 |     console.log('');
100 | 
101 |     // Test 4: List Records from Requests table
102 |     console.log('📄 Test 4: Listing Records (Requests Table)');
103 |     console.log('-------------------------------------------');
104 |     const recordsResponse = await makeMCPRequest('tools/call', {
105 |       name: 'list_records',
106 |       arguments: {
107 |         table_name: 'requests',
108 |         max_records: 3
109 |       }
110 |     });
111 |     console.log('✅ Records Response:');
112 |     console.log(JSON.stringify(recordsResponse, null, 2));
113 |     console.log('');
114 | 
115 |     // Test 5: List Records from Providers table
116 |     console.log('👥 Test 5: Listing Records (Providers Table)');
117 |     console.log('--------------------------------------------');
118 |     const providersResponse = await makeMCPRequest('tools/call', {
119 |       name: 'list_records',
120 |       arguments: {
121 |         table_name: 'providers',
122 |         max_records: 3
123 |       }
124 |     });
125 |     console.log('✅ Providers Response:');
126 |     console.log(JSON.stringify(providersResponse, null, 2));
127 |     console.log('');
128 | 
129 |     // Test 6: List Records from Categories table
130 |     console.log('🏷️ Test 6: Listing Records (Categories Table)');
131 |     console.log('---------------------------------------------');
132 |     const categoriesResponse = await makeMCPRequest('tools/call', {
133 |       name: 'list_records',
134 |       arguments: {
135 |         table_name: 'categories',
136 |         max_records: 3
137 |       }
138 |     });
139 |     console.log('✅ Categories Response:');
140 |     console.log(JSON.stringify(categoriesResponse, null, 2));
141 |     console.log('');
142 | 
143 |     console.log('🎉 All Tests Completed Successfully!');
144 |     console.log('');
145 |     console.log('📊 Test Summary:');
146 |     console.log('✅ MCP Server is running and accessible');
147 |     console.log('✅ Airtable API connection is working');
148 |     console.log('✅ All MCP tools are functioning properly');
149 |     console.log('✅ JSON-RPC protocol is correctly implemented');
150 |     console.log('✅ Error handling is working');
151 |     console.log('');
152 |     console.log('🚀 The Airtable MCP is ready for use!');
153 | 
154 |   } catch (error) {
155 |     console.error('❌ Test failed:', error.message);
156 |     process.exit(1);
157 |   }
158 | }
159 | 
160 | // Run the comprehensive test
161 | runComprehensiveTest();
162 | 
163 | 
```

--------------------------------------------------------------------------------
/docs/guides/INSTALLATION.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Installation
  2 | 
  3 | Airtable MCP embeds Airtable database connectivity directly into your AI-powered code editor
  4 | 
  5 | ## Getting Started
  6 | 
  7 | Built by Rashid Azarang,
  8 | 
  9 | Airtable MCP gives AI code editors and agents the ability to access and manipulate your Airtable databases for powerful data management capabilities - all in a secure manner with your own API tokens.
 10 | 
 11 | With this MCP server tool, you can enable AI code editors and agents to have access to:
 12 | 
 13 | * List and access all your Airtable bases
 14 | * Browse tables, fields, and record data
 15 | * Create, read, update, and delete records
 16 | * Export and manipulate schemas
 17 | * Perform complex queries against your data
 18 | * Create data migration mappings
 19 | * Analyze and transform your Airtable data
 20 | 
 21 | That way, you can simply tell Cursor or any AI code editor with MCP integrations:
 22 | 
 23 | "Show me all the tables in my Airtable base"
 24 | 
 25 | "Find all records from the Customers table where the status is Active and the last purchase was after January 1st"
 26 | 
 27 | "Create a new record in the Products table with these fields..."
 28 | 
 29 | "Export the schema of my current Airtable base"
 30 | 
 31 | "Help me create a mapping between these two tables for data migration"
 32 | 
 33 | ---
 34 | 
 35 | ## Requirements
 36 | 
 37 | * Node.js 14+ installed on your machine
 38 | * Python 3.10+ installed on your machine (automatically detected)
 39 | * Airtable Personal Access Token (API Key)
 40 | * MCP Client Application (Cursor, Claude Desktop, Cline, Zed, etc.)
 41 | 
 42 | **Note**: Model Context Protocol (MCP) is specific to Anthropic models. When using an editor like Cursor, make sure to enable composer agent with Claude 3.5 Sonnet selected as the model.
 43 | 
 44 | ---
 45 | 
 46 | ## Installation
 47 | 
 48 | ### 1. Install via Smithery (Easiest)
 49 | 
 50 | The easiest way to install Airtable MCP is through Smithery:
 51 | 
 52 | 1. Visit [Smithery](https://smithery.ai)
 53 | 2. Search for "@rashidazarang/airtable-mcp"
 54 | 3. Click "Install" and follow the prompts to configure with your Airtable token and base ID
 55 | 
 56 | ### 2. Install via NPX (Alternative)
 57 | 
 58 | Another simple way to install and use Airtable MCP is via NPX:
 59 | 
 60 | ```bash
 61 | # Install globally
 62 | npm install -g airtable-mcp
 63 | 
 64 | # Or use directly with npx (no installation needed)
 65 | npx airtable-mcp --token YOUR_AIRTABLE_TOKEN --base YOUR_BASE_ID
 66 | ```
 67 | 
 68 | ### 3. Get Your Airtable API Token
 69 | 
 70 | 1. Log in to your Airtable account
 71 | 2. Go to your [Account Settings](https://airtable.com/account)
 72 | 3. Navigate to the "API" section
 73 | 4. Create a Personal Access Token with appropriate permissions
 74 | 5. Copy your token to use in the configuration
 75 | 
 76 | ### 4. Configure Your MCP Client
 77 | 
 78 | #### For Cursor:
 79 | 
 80 | 1. Go to Cursor Settings
 81 | 2. Navigate to Features, scroll down to MCP Servers and click "Add new MCP server"
 82 | 3. Give it a unique name (airtable-tools), set type to "command" and set the command to:
 83 | 
 84 | **For macOS/Linux/Windows:**
 85 | ```bash
 86 | npx airtable-mcp --token YOUR_AIRTABLE_TOKEN --base YOUR_BASE_ID
 87 | ```
 88 | 
 89 | Replace `YOUR_AIRTABLE_TOKEN` with your Airtable Personal Access Token and `YOUR_BASE_ID` with your default Airtable base ID (optional).
 90 | 
 91 | #### For Advanced Users via ~/.cursor/mcp.json:
 92 | 
 93 | Edit your `~/.cursor/mcp.json` file to include:
 94 | 
 95 | ```json
 96 | {
 97 |   "mcpServers": {
 98 |     "airtable-tools": {
 99 |       "command": "npx",
100 |       "args": [
101 |         "airtable-mcp",
102 |         "--token", "YOUR_AIRTABLE_TOKEN",
103 |         "--base", "YOUR_BASE_ID"
104 |       ]
105 |     }
106 |   }
107 | }
108 | ```
109 | 
110 | ### 5. Verify Connection
111 | 
112 | 1. Restart your MCP client (Cursor, etc.)
113 | 2. Create a new query using the Composer Agent with Claude 3.5 Sonnet model
114 | 3. Ask something like "List my Airtable bases" or "Show me the tables in my current base"
115 | 4. You should see a response with your Airtable data
116 | 
117 | ### 6. For Production Use (Optional)
118 | 
119 | For continuous availability, you can set up Airtable MCP using PM2:
120 | 
121 | ```bash
122 | # Install PM2 if you don't have it
123 | npm install -g pm2
124 | 
125 | # Create a PM2 config file
126 | echo 'module.exports = {
127 |   apps: [
128 |     {
129 |       name: "airtable-mcp",
130 |       script: "npx",
131 |       args: [
132 |         "airtable-mcp",
133 |         "--token", "YOUR_AIRTABLE_TOKEN",
134 |         "--base", "YOUR_BASE_ID"
135 |       ],
136 |       env: {
137 |         PATH: process.env.PATH,
138 |       },
139 |     },
140 |   ],
141 | };' > ecosystem.config.js
142 | 
143 | # Start the process
144 | pm2 start ecosystem.config.js
145 | 
146 | # Set it to start on boot
147 | pm2 startup
148 | pm2 save
149 | ```
150 | 
151 | ---
152 | 
153 | ## Troubleshooting
154 | 
155 | Here are some common issues and their solutions:
156 | 
157 | ### Error: Unable to connect to Airtable API
158 | 
159 | - Double-check your Airtable API token is correct and has sufficient permissions
160 | - Verify your internet connection
161 | - Check if Airtable API is experiencing downtime
162 | 
163 | ### Issue: MCP server not connecting
164 | 
165 | - Make sure Node.js 14+ and Python 3.10+ are installed and in your PATH
166 | - Try specifying a specific version: `npx airtable-mcp@latest`
167 | - Check the Cursor logs for any connection errors
168 | 
169 | ### Error: Base not found
170 | 
171 | - Verify your base ID is correct
172 | - Make sure your API token has access to the specified base
173 | - Try listing all bases first to confirm access
174 | 
175 | ### Issue: Permission denied errors
176 | 
177 | - Make sure your token has the necessary permissions for the operations you're trying to perform
178 | - Check if you're attempting operations on tables/bases that your token doesn't have access to
179 | 
180 | ### For more help
181 | 
182 | - Open an issue on the [GitHub repository](https://github.com/rashidazarang/airtable-mcp/issues)
183 | - Check the Airtable API documentation for any API-specific errors 
```

--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | const path = require('path');
  4 | const { execSync } = require('child_process');
  5 | const { spawn } = require('child_process');
  6 | 
  7 | // Polyfill for AbortController in older Node.js versions
  8 | if (typeof globalThis.AbortController === 'undefined') {
  9 |   globalThis.AbortController = class AbortController {
 10 |     constructor() {
 11 |       this.signal = {
 12 |         aborted: false,
 13 |         addEventListener: () => {},
 14 |         removeEventListener: () => {},
 15 |         dispatchEvent: () => true
 16 |       };
 17 |     }
 18 |     abort() {
 19 |       this.signal.aborted = true;
 20 |     }
 21 |   };
 22 |   console.log('ℹ️ Added AbortController polyfill for compatibility with older Node.js versions');
 23 | }
 24 | 
 25 | // Parse command-line arguments
 26 | const args = process.argv.slice(2);
 27 | let tokenIndex = args.indexOf('--token');
 28 | let baseIndex = args.indexOf('--base');
 29 | let configIndex = args.indexOf('--config');
 30 | 
 31 | // Extract token, base ID and config
 32 | const token = tokenIndex !== -1 && tokenIndex + 1 < args.length ? args[tokenIndex + 1] : null;
 33 | const baseId = baseIndex !== -1 && baseIndex + 1 < args.length ? args[baseIndex + 1] : null;
 34 | const config = configIndex !== -1 && configIndex + 1 < args.length ? args[configIndex + 1] : null;
 35 | 
 36 | console.log('🔌 Airtable MCP - Connecting your AI to Airtable');
 37 | console.log('-----------------------------------------------');
 38 | 
 39 | // Find Python interpreter
 40 | const getPythonPath = () => {
 41 |   try {
 42 |     const whichPython = execSync('which python3.10').toString().trim();
 43 |     return whichPython;
 44 |   } catch (e) {
 45 |     try {
 46 |       const whichPython = execSync('which python3').toString().trim();
 47 |       return whichPython;
 48 |     } catch (e) {
 49 |       return 'python';
 50 |     }
 51 |   }
 52 | };
 53 | 
 54 | // Check Python version
 55 | const checkPythonVersion = (pythonPath) => {
 56 |   try {
 57 |     const versionStr = execSync(`${pythonPath} --version`).toString().trim();
 58 |     const versionMatch = versionStr.match(/Python (\d+)\.(\d+)/);
 59 |     if (versionMatch) {
 60 |       const major = parseInt(versionMatch[1]);
 61 |       const minor = parseInt(versionMatch[2]);
 62 |       return (major > 3 || (major === 3 && minor >= 10));
 63 |     }
 64 |     return false;
 65 |   } catch (e) {
 66 |     return false;
 67 |   }
 68 | };
 69 | 
 70 | const pythonPath = getPythonPath();
 71 | 
 72 | // Verify Python compatibility
 73 | if (!checkPythonVersion(pythonPath)) {
 74 |   console.error('❌ Error: MCP SDK requires Python 3.10+');
 75 |   console.error('Please install Python 3.10 or newer and try again.');
 76 |   process.exit(1);
 77 | }
 78 | 
 79 | // We now use inspector_server.py instead of server.py
 80 | const serverScript = path.join(__dirname, 'inspector_server.py');
 81 | 
 82 | // Check if the script exists
 83 | try {
 84 |   require('fs').accessSync(serverScript, require('fs').constants.F_OK);
 85 | } catch (e) {
 86 |   console.error(`❌ Error: Could not find server script at ${serverScript}`);
 87 |   console.error('Please make sure you have the complete package installed.');
 88 |   process.exit(1);
 89 | }
 90 | 
 91 | // Prepare arguments for the Python script
 92 | const scriptArgs = [serverScript];
 93 | if (token) {
 94 |   scriptArgs.push('--token', token);
 95 | }
 96 | if (baseId) {
 97 |   scriptArgs.push('--base', baseId);
 98 | }
 99 | if (config) {
100 |   scriptArgs.push('--config', config);
101 |   
102 |   // Try to extract and log info from config
103 |   try {
104 |     const configObj = JSON.parse(config);
105 |     if (configObj.airtable_token) {
106 |       console.log('✅ Using API token from config');
107 |     }
108 |     if (configObj.base_id) {
109 |       console.log(`✅ Using base ID from config: ${configObj.base_id}`);
110 |     }
111 |   } catch (e) {
112 |     console.warn('⚠️ Could not parse config JSON, attempting to sanitize...');
113 |     
114 |     // Sanitize config JSON - fix common formatting issues
115 |     try {
116 |       // Remove any unexpected line breaks, extra quotes, and escape characters
117 |       const sanitizedConfig = config
118 |         .replace(/[\r\n]+/g, '')
119 |         .replace(/\\+"/g, '"')
120 |         .replace(/^"/, '')
121 |         .replace(/"$/, '')
122 |         .replace(/\\/g, '');
123 |       
124 |       // Try parsing it
125 |       const configObj = JSON.parse(sanitizedConfig);
126 |       if (configObj) {
127 |         console.log('✅ Successfully sanitized config JSON');
128 |         // Update config with sanitized version
129 |         scriptArgs[scriptArgs.indexOf(config)] = sanitizedConfig;
130 |         config = sanitizedConfig;
131 |         
132 |         if (configObj.airtable_token) {
133 |           console.log('✅ Using API token from sanitized config');
134 |         }
135 |         if (configObj.base_id) {
136 |           console.log(`✅ Using base ID from sanitized config: ${configObj.base_id}`);
137 |         }
138 |       }
139 |     } catch (sanitizeErr) {
140 |       console.warn('⚠️ Could not sanitize config JSON, passing it directly to Python script');
141 |     }
142 |   }
143 | } else {
144 |   if (token) {
145 |     console.log('✅ Using provided API token');
146 |   } else {
147 |     console.log('⚠️ No API token provided, will try to use .env file');
148 |   }
149 | 
150 |   if (baseId) {
151 |     console.log(`✅ Using base ID: ${baseId}`);
152 |   } else {
153 |     console.log('ℹ️ No base ID provided, will need to set one later');
154 |   }
155 | }
156 | 
157 | // Execute the Python script
158 | const serverProcess = spawn(pythonPath, scriptArgs, {
159 |   stdio: 'inherit',
160 | });
161 | 
162 | // Handle process exit
163 | serverProcess.on('close', (code) => {
164 |   if (code !== 0) {
165 |     console.error(`❌ Airtable MCP server exited with code ${code}`);
166 |   }
167 |   process.exit(code);
168 | });
169 | 
170 | // Handle signals
171 | process.on('SIGINT', () => {
172 |   console.log('\n👋 Shutting down Airtable MCP server...');
173 |   serverProcess.kill('SIGINT');
174 | });
175 | 
176 | process.on('SIGTERM', () => {
177 |   console.log('\n👋 Shutting down Airtable MCP server...');
178 |   serverProcess.kill('SIGTERM');
179 | }); 
```

--------------------------------------------------------------------------------
/ISSUE_RESPONSES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # GitHub Issue Responses
  2 | 
  3 | ## Issue #7: Personal Access Token Leakage
  4 | 
  5 | Thank you for responsibly disclosing this security vulnerability. This has been fixed in v1.2.4.
  6 | 
  7 | ### Actions Taken:
  8 | ✅ Removed all hardcoded tokens from test files
  9 | ✅ Updated code to require environment variables for credentials
 10 | ✅ Added SECURITY_NOTICE.md with rotation instructions
 11 | ✅ The exposed tokens have been invalidated
 12 | 
 13 | ### Changes Made:
 14 | - `test_client.py` - Now uses environment variables
 15 | - `test_mcp_comprehensive.js` - Now uses environment variables
 16 | - Added `.env.example` file for secure configuration
 17 | - Updated documentation with security best practices
 18 | 
 19 | All users should update to v1.2.4 immediately. The exposed tokens are no longer valid, and users must use their own Airtable credentials.
 20 | 
 21 | Thank you for helping improve the security of this project!
 22 | 
 23 | ---
 24 | 
 25 | ## Issue #6: [Server Bug] @rashidazarang/airtable-mcp
 26 | 
 27 | This issue has been resolved in v1.2.4! 
 28 | 
 29 | ### Root Cause:
 30 | The Smithery configuration was using the Python implementation which had compatibility issues with MCP 1.4.1.
 31 | 
 32 | ### Solution:
 33 | - Updated `smithery.yaml` to use the stable JavaScript implementation (`airtable_simple.js`)
 34 | - Fixed authentication flow to properly handle credentials
 35 | - Added proper environment variable support
 36 | 
 37 | ### To fix:
 38 | 1. Update to v1.2.4: `npm install @rashidazarang/airtable-mcp@latest`
 39 | 2. Reconfigure with your credentials as shown in the updated README
 40 | 3. Restart Claude Desktop
 41 | 
 42 | The server should now connect properly without the "API key is required" error.
 43 | 
 44 | ---
 45 | 
 46 | ## Issue #5: When Using Smithy, throwing 'Streamable HTTP error: Error POSTing to endpoint (HTTP 400): null'
 47 | 
 48 | Fixed in v1.2.4! This was the same root cause as Issue #6.
 49 | 
 50 | ### What was wrong:
 51 | - The Python server had MCP compatibility issues
 52 | - Authentication wasn't being handled correctly
 53 | - The Smithery configuration was misconfigured
 54 | 
 55 | ### What we fixed:
 56 | - Switched to the JavaScript implementation
 57 | - Updated smithery.yaml with proper configuration
 58 | - Fixed credential passing through Smithery
 59 | 
 60 | ### How to resolve:
 61 | ```bash
 62 | # Update to latest version
 63 | npm install -g @rashidazarang/airtable-mcp@latest
 64 | 
 65 | # Or if using Smithery directly:
 66 | npx @smithery/cli install @rashidazarang/airtable-mcp --update
 67 | ```
 68 | 
 69 | Then reconfigure with your Airtable credentials. The HTTP 400 errors should be resolved.
 70 | 
 71 | ---
 72 | 
 73 | ## Issue #4: Glama listing is missing Dockerfile
 74 | 
 75 | Fixed in v1.2.4!
 76 | 
 77 | ### Changes:
 78 | - Created `Dockerfile.node` specifically for Node.js deployment
 79 | - Updated `smithery.yaml` to reference the correct Dockerfile
 80 | - The original Dockerfile is retained for backward compatibility
 81 | 
 82 | The Dockerfile is now included and properly configured for cloud deployments.
 83 | 
 84 | ---
 85 | 
 86 | # GitHub Release Text for v1.2.4
 87 | 
 88 | ## 🚨 Critical Security Release - v1.2.4
 89 | 
 90 | ### ⚠️ IMPORTANT SECURITY FIX
 91 | 
 92 | This release addresses a **critical security vulnerability** where API tokens were hardcoded in test files. All users should update immediately.
 93 | 
 94 | ### 🔒 Security Fixes
 95 | - **Removed hardcoded API tokens** from all test files (fixes #7)
 96 | - Test files now require environment variables for credentials
 97 | - Added comprehensive security documentation
 98 | - Previously exposed tokens have been invalidated
 99 | 
100 | ### 🐛 Bug Fixes
101 | - **Fixed Smithery deployment issues** (fixes #5, #6)
102 |   - Resolved HTTP 400 errors when connecting through Smithery
103 |   - Fixed "API key is required for remote connections" error
104 |   - Switched to stable JavaScript implementation for cloud deployments
105 | - **Added missing Dockerfile** for Glama listing (fixes #4)
106 | 
107 | ### ✨ Improvements
108 | - Added environment variable support for secure credential management
109 | - Improved logging with configurable levels (ERROR, WARN, INFO, DEBUG)
110 | - Enhanced error messages for better debugging
111 | - Updated documentation with clear setup instructions
112 | 
113 | ### 📦 What's Changed
114 | - `test_client.py` - Now uses environment variables
115 | - `test_mcp_comprehensive.js` - Now uses environment variables  
116 | - `airtable_simple.js` - Added env variable and logging support
117 | - `smithery.yaml` - Fixed to use JavaScript implementation
118 | - `Dockerfile.node` - New optimized Docker image for Node.js
119 | - `SECURITY_NOTICE.md` - Important security information
120 | - `README.md` - Complete rewrite with better instructions
121 | 
122 | ### 💔 Breaking Changes
123 | Test files now require environment variables:
124 | ```bash
125 | export AIRTABLE_TOKEN="your_token"
126 | export AIRTABLE_BASE_ID="your_base_id"
127 | ```
128 | 
129 | ### 📋 Migration Instructions
130 | 
131 | 1. **Update to v1.2.4:**
132 |    ```bash
133 |    npm install -g @rashidazarang/airtable-mcp@latest
134 |    ```
135 | 
136 | 2. **Set up environment variables:**
137 |    ```bash
138 |    export AIRTABLE_TOKEN="your_personal_token"
139 |    export AIRTABLE_BASE_ID="your_base_id"
140 |    ```
141 | 
142 | 3. **Update your MCP configuration** (see README for details)
143 | 
144 | 4. **Restart your MCP client**
145 | 
146 | ### 🙏 Acknowledgments
147 | Special thanks to @BXXC-SDXZ for responsibly disclosing the security vulnerability, and to @ricklesgibson and @punkpeye for reporting the deployment issues.
148 | 
149 | ### ⚠️ Security Note
150 | If you were using the previously exposed tokens, they have been revoked. You must use your own Airtable credentials going forward.
151 | 
152 | **Full Changelog**: https://github.com/rashidazarang/airtable-mcp/compare/v1.2.3...v1.2.4
153 | 
154 | ---
155 | 
156 | ## NPM Publish Commands
157 | 
158 | ```bash
159 | # Make sure you're logged in to npm
160 | npm login
161 | 
162 | # Update version (already done in package.json)
163 | npm version 1.2.4
164 | 
165 | # Publish to npm
166 | npm publish --access public
167 | 
168 | # Create git tag
169 | git tag -a v1.2.4 -m "Critical security fix and Smithery deployment fixes"
170 | git push origin v1.2.4
171 | ```
```

--------------------------------------------------------------------------------
/src/typescript/app/config.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import fs from 'node:fs';
  2 | import path from 'node:path';
  3 | import { createHash } from 'node:crypto';
  4 | import { config as loadEnv } from 'dotenv';
  5 | import { governanceOutputSchema, GovernanceSnapshot } from './types';
  6 | import { GovernanceError } from '../errors';
  7 | 
  8 | loadEnv();
  9 | 
 10 | export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
 11 | 
 12 | export interface AirtableAuthConfig {
 13 |   personalAccessToken: string;
 14 |   patHash: string;
 15 |   defaultBaseId?: string;
 16 |   allowedBases: string[];
 17 | }
 18 | 
 19 | export interface AppConfig {
 20 |   version: string;
 21 |   auth: AirtableAuthConfig;
 22 |   governance: GovernanceSnapshot;
 23 |   logLevel: LogLevel;
 24 |   exceptionQueueSize: number;
 25 | }
 26 | 
 27 | const DEFAULT_EXCEPTION_QUEUE_SIZE = 500;
 28 | 
 29 | function parseCsv(value?: string | null): string[] {
 30 |   if (!value) {
 31 |     return [];
 32 |   }
 33 |   return value
 34 |     .split(',')
 35 |     .map((entry) => entry.trim())
 36 |     .filter((entry) => entry.length > 0);
 37 | }
 38 | 
 39 | function hashSecret(secret: string): string {
 40 |   return createHash('sha256').update(secret).digest('hex').slice(0, 12);
 41 | }
 42 | 
 43 | function resolveLogLevel(): LogLevel {
 44 |   const raw = (process.env.LOG_LEVEL || 'info').toLowerCase();
 45 |   if (raw === 'error' || raw === 'warn' || raw === 'info' || raw === 'debug') {
 46 |     return raw;
 47 |   }
 48 |   return 'info';
 49 | }
 50 | 
 51 | function determineAllowedBases(defaultBaseId?: string): string[] {
 52 |   const fromEnv = parseCsv(process.env.AIRTABLE_ALLOWED_BASES || process.env.AIRTABLE_BASE_ALLOWLIST);
 53 |   const baseSet = new Set<string>();
 54 |   if (defaultBaseId) {
 55 |     baseSet.add(defaultBaseId);
 56 |   }
 57 |   fromEnv.forEach((base) => baseSet.add(base));
 58 |   // Allow empty base list - users can use list_bases tool to discover bases
 59 |   // and then specify them dynamically in tool calls
 60 |   return Array.from(baseSet);
 61 | }
 62 | 
 63 | function parseAllowedTables(raw?: string | null): Array<{ baseId: string; table: string }> {
 64 |   if (!raw) {
 65 |     return [];
 66 |   }
 67 |   const tables: Array<{ baseId: string; table: string }> = [];
 68 |   for (const entry of raw.split(',')) {
 69 |     const trimmed = entry.trim();
 70 |     if (!trimmed) continue;
 71 |     const [baseId, table] = trimmed.split(':');
 72 |     if (!baseId || !table) {
 73 |       throw new GovernanceError(
 74 |         `Invalid AIRTABLE_ALLOWED_TABLES entry "${trimmed}". Expected format baseId:tableName.`
 75 |       );
 76 |     }
 77 |     tables.push({ baseId: baseId.trim(), table: table.trim() });
 78 |   }
 79 |   return tables;
 80 | }
 81 | 
 82 | function readGovernanceFile(): Partial<GovernanceSnapshot> | undefined {
 83 |   const explicitPath = process.env.AIRTABLE_GOVERNANCE_PATH;
 84 |   const fallbackPath = path.resolve(process.cwd(), 'config', 'governance.json');
 85 | 
 86 |   const filePath = explicitPath || fallbackPath;
 87 |   if (!fs.existsSync(filePath)) {
 88 |     return undefined;
 89 |   }
 90 | 
 91 |   try {
 92 |     const raw = fs.readFileSync(filePath, 'utf8');
 93 |     const parsed = JSON.parse(raw);
 94 |     const partialSchema = governanceOutputSchema.partial();
 95 |     const result = partialSchema.parse(parsed) as Partial<GovernanceSnapshot>;
 96 |     return result;
 97 |   } catch (error) {
 98 |     throw new GovernanceError(
 99 |       `Failed to parse governance configuration at ${filePath}: ${error instanceof Error ? error.message : String(error)}`
100 |     );
101 |   }
102 | }
103 | 
104 | function buildGovernanceSnapshot(allowedBases: string[]): GovernanceSnapshot {
105 |   const baseSnapshot: GovernanceSnapshot = {
106 |     allowedBases,
107 |     allowedTables: [],
108 |     allowedOperations: ['describe', 'query', 'create', 'update', 'upsert'],
109 |     piiFields: [],
110 |     redactionPolicy: 'mask_on_inline',
111 |     loggingPolicy: 'minimal',
112 |     retentionDays: 7
113 |   };
114 | 
115 |   const overrides = readGovernanceFile();
116 | 
117 |   const envAllowedTables = parseAllowedTables(process.env.AIRTABLE_ALLOWED_TABLES);
118 | 
119 |   const merged: GovernanceSnapshot = {
120 |     ...baseSnapshot,
121 |     ...(overrides ?? {})
122 |   };
123 | 
124 |   // Ensure allow-lists include env tables/bases.
125 |   const bases = new Set<string>(merged.allowedBases);
126 |   allowedBases.forEach((base) => bases.add(base));
127 |   merged.allowedBases = Array.from(bases);
128 | 
129 |   if (overrides?.allowedTables || envAllowedTables.length > 0) {
130 |     const tableSet = new Map<string, { baseId: string; table: string }>();
131 |     (overrides?.allowedTables ?? []).forEach((table) => {
132 |       tableSet.set(`${table.baseId}:${table.table}`, table);
133 |     });
134 |     envAllowedTables.forEach((table) => {
135 |       tableSet.set(`${table.baseId}:${table.table}`, table);
136 |     });
137 |     merged.allowedTables = Array.from(tableSet.values());
138 |   }
139 | 
140 |   return governanceOutputSchema.parse(merged);
141 | }
142 | 
143 | export function loadConfig(): AppConfig {
144 |   const personalAccessToken =
145 |     process.env.AIRTABLE_PAT ||
146 |     process.env.AIRTABLE_TOKEN ||
147 |     process.env.AIRTABLE_API_TOKEN ||
148 |     process.env.AIRTABLE_API_KEY;
149 | 
150 |   if (!personalAccessToken) {
151 |     throw new GovernanceError(
152 |       'Missing Airtable credentials. Set AIRTABLE_PAT (preferred) or AIRTABLE_TOKEN.'
153 |     );
154 |   }
155 | 
156 |   const defaultBaseId = process.env.AIRTABLE_DEFAULT_BASE ?? process.env.AIRTABLE_BASE_ID ?? process.env.AIRTABLE_BASE;
157 |   const allowedBases = determineAllowedBases(defaultBaseId);
158 |   const governance = buildGovernanceSnapshot(allowedBases);
159 | 
160 |   const auth: AirtableAuthConfig = {
161 |     personalAccessToken,
162 |     patHash: hashSecret(personalAccessToken),
163 |     allowedBases
164 |   };
165 |   if (defaultBaseId) {
166 |     auth.defaultBaseId = defaultBaseId;
167 |   }
168 | 
169 |   return {
170 |     version: process.env.npm_package_version || '0.0.0',
171 |     auth,
172 |     governance,
173 |     logLevel: resolveLogLevel(),
174 |     exceptionQueueSize:
175 |       Number.parseInt(process.env.EXCEPTION_QUEUE_SIZE || '', 10) > 0
176 |         ? Number.parseInt(process.env.EXCEPTION_QUEUE_SIZE as string, 10)
177 |         : DEFAULT_EXCEPTION_QUEUE_SIZE
178 |   };
179 | }
180 | 
```

--------------------------------------------------------------------------------
/examples/env-demo.js:
--------------------------------------------------------------------------------

```javascript
  1 | #!/usr/bin/env node
  2 | 
  3 | /**
  4 |  * Demo script that uses the AIRTABLE_BASE_ID from the .env file
  5 |  * Demonstrates various operations with the Airtable API
  6 |  */
  7 | 
  8 | require('dotenv').config();
  9 | const baseUtils = require('../tools/airtable-base');
 10 | const crudUtils = require('../tools/airtable-crud');
 11 | const schemaUtils = require('../tools/airtable-schema');
 12 | 
 13 | // Constants
 14 | const DEMO_TABLE_NAME = 'ENV Demo Table';
 15 | const SAMPLE_RECORDS = [
 16 |   { Name: 'Record from ENV Demo', Description: 'Created using AIRTABLE_BASE_ID from .env file', Status: 'Active' },
 17 |   { Name: 'Another ENV Record', Description: 'Second record from the environment demo', Status: 'Pending' }
 18 | ];
 19 | 
 20 | async function runDemo() {
 21 |   console.log('=================================');
 22 |   console.log('     AIRTABLE ENV DEMO SCRIPT    ');
 23 |   console.log('=================================');
 24 |   
 25 |   // Check environment variables
 26 |   if (!process.env.AIRTABLE_PERSONAL_ACCESS_TOKEN) {
 27 |     console.error('❌ Error: AIRTABLE_PERSONAL_ACCESS_TOKEN is not set in .env file');
 28 |     process.exit(1);
 29 |   }
 30 |   
 31 |   if (!process.env.AIRTABLE_BASE_ID) {
 32 |     console.error('❌ Error: AIRTABLE_BASE_ID is not set in .env file');
 33 |     process.exit(1);
 34 |   }
 35 |   
 36 |   const baseId = process.env.AIRTABLE_BASE_ID;
 37 |   console.log(`✅ Using AIRTABLE_BASE_ID: ${baseId}`);
 38 |   
 39 |   try {
 40 |     // Step 1: Verify base access
 41 |     console.log('\nStep 1: Verifying access to the base...');
 42 |     const baseAccess = await baseUtils.checkBaseAccess(baseId);
 43 |     
 44 |     if (!baseAccess.accessible) {
 45 |       console.error(`❌ Error: Cannot access base with ID ${baseId}`);
 46 |       console.error(`   Reason: ${baseAccess.error}`);
 47 |       process.exit(1);
 48 |     }
 49 |     
 50 |     console.log(`✅ Access confirmed to base: ${baseAccess.name}`);
 51 |     
 52 |     // Step 2: List existing tables
 53 |     console.log('\nStep 2: Listing existing tables...');
 54 |     const tables = await baseUtils.listTables(baseId);
 55 |     console.log(`✅ Found ${tables.length} tables in the base`);
 56 |     
 57 |     // Step 3: Check if our demo table exists
 58 |     console.log('\nStep 3: Checking if demo table exists...');
 59 |     const demoTableExists = await crudUtils.tableExists(baseId, DEMO_TABLE_NAME);
 60 |     
 61 |     if (demoTableExists) {
 62 |       console.log(`✅ Demo table "${DEMO_TABLE_NAME}" already exists`);
 63 |     } else {
 64 |       console.log(`ℹ️ Demo table "${DEMO_TABLE_NAME}" does not exist, creating it...`);
 65 |       
 66 |       // Step 4: Create the demo table
 67 |       console.log('\nStep 4: Creating the demo table...');
 68 |       const tableConfig = {
 69 |         name: DEMO_TABLE_NAME,
 70 |         description: 'Table created from the Environment Demo script',
 71 |         fields: [
 72 |           {
 73 |             name: 'Name',
 74 |             type: 'singleLineText',
 75 |             description: 'Record name'
 76 |           },
 77 |           {
 78 |             name: 'Description',
 79 |             type: 'multilineText',
 80 |             description: 'Record description'
 81 |           },
 82 |           {
 83 |             name: 'Status',
 84 |             type: 'singleSelect',
 85 |             options: {
 86 |               choices: [
 87 |                 { name: 'Active' },
 88 |                 { name: 'Pending' },
 89 |                 { name: 'Completed' }
 90 |               ]
 91 |             },
 92 |             description: 'Current status'
 93 |           },
 94 |           {
 95 |             name: 'Created',
 96 |             type: 'date',
 97 |             options: {
 98 |               dateFormat: {
 99 |                 name: 'local'
100 |               }
101 |             },
102 |             description: 'Creation date'
103 |           }
104 |         ]
105 |       };
106 |       
107 |       await schemaUtils.createTable(baseId, tableConfig);
108 |       console.log(`✅ Created demo table: ${DEMO_TABLE_NAME}`);
109 |     }
110 |     
111 |     // Step 5: Create sample records
112 |     console.log('\nStep 5: Creating sample records...');
113 |     // Add today's date to all records
114 |     const recordsWithDate = SAMPLE_RECORDS.map(record => ({
115 |       ...record,
116 |       Created: new Date().toISOString().split('T')[0] // Format as YYYY-MM-DD
117 |     }));
118 |     
119 |     const createdRecords = await crudUtils.createRecords(baseId, DEMO_TABLE_NAME, recordsWithDate);
120 |     console.log(`✅ Created ${createdRecords.length} sample records`);
121 |     
122 |     // Step 6: Read records back
123 |     console.log('\nStep 6: Reading records from the table...');
124 |     const records = await crudUtils.readRecords(baseId, DEMO_TABLE_NAME, 100);
125 |     console.log(`✅ Read ${records.length} records from the table`);
126 |     
127 |     console.log('\nSample record:');
128 |     console.log(JSON.stringify(records[0], null, 2));
129 |     
130 |     // Step 7: Update a record
131 |     console.log('\nStep 7: Updating the first record...');
132 |     const recordToUpdate = {
133 |       id: createdRecords[0].id,
134 |       fields: {
135 |         Description: createdRecords[0].Description + ' (UPDATED)',
136 |         Status: 'Completed'
137 |       }
138 |     };
139 |     
140 |     const updatedRecords = await crudUtils.updateRecords(baseId, DEMO_TABLE_NAME, [recordToUpdate]);
141 |     console.log(`✅ Updated ${updatedRecords.length} record`);
142 |     
143 |     // Step 8: Get the updated record
144 |     console.log('\nStep 8: Getting the updated record...');
145 |     const updatedRecord = await crudUtils.getRecord(baseId, DEMO_TABLE_NAME, createdRecords[0].id);
146 |     console.log('Updated record:');
147 |     console.log(JSON.stringify(updatedRecord, null, 2));
148 |     
149 |     // Step 9: Demonstrate filtering records
150 |     console.log('\nStep 9: Filtering records by status...');
151 |     const completedRecords = await crudUtils.readRecords(baseId, DEMO_TABLE_NAME, 100, 'Status="Completed"');
152 |     console.log(`✅ Found ${completedRecords.length} records with Status="Completed"`);
153 |     
154 |     console.log('\n=================================');
155 |     console.log('      ENV DEMO COMPLETED         ');
156 |     console.log('=================================');
157 |     console.log('\nThis script demonstrated:');
158 |     console.log('1. Loading environment variables from .env file');
159 |     console.log('2. Accessing an Airtable base using AIRTABLE_BASE_ID');
160 |     console.log('3. Creating a table (if it doesn\'t exist)');
161 |     console.log('4. Creating, reading, and updating records');
162 |     console.log('5. Filtering records using Airtable formulas');
163 |     console.log('\nAll operations used the AIRTABLE_BASE_ID environment variable');
164 |     
165 |   } catch (error) {
166 |     console.error(`❌ Error: ${error.message}`);
167 |     process.exit(1);
168 |   }
169 | }
170 | 
171 | // Run the demo
172 | runDemo(); 
```

--------------------------------------------------------------------------------
/src/typescript/app/tools/describe.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
  2 | import {
  3 |   DescribeInput,
  4 |   DescribeOutput,
  5 |   describeInputSchema,
  6 |   describeInputShape,
  7 |   describeOutputSchema
  8 | } from '../types';
  9 | import { AppContext } from '../context';
 10 | import { GovernanceError, NotFoundError } from '../../errors';
 11 | import { handleToolError } from './handleError';
 12 | 
 13 | type DescribeTableEntry = NonNullable<DescribeOutput['tables']>[number];
 14 | type DescribeFieldEntry = NonNullable<DescribeTableEntry['fields']>[number];
 15 | type DescribeViewEntry = NonNullable<DescribeTableEntry['views']>[number];
 16 | 
 17 | function normalizeField(raw: unknown): DescribeFieldEntry {
 18 |   const source = raw as Record<string, unknown>;
 19 |   const field: DescribeFieldEntry = {
 20 |     id: String(source?.id ?? ''),
 21 |     name: String(source?.name ?? ''),
 22 |     type: String(source?.type ?? '')
 23 |   };
 24 |   if (source?.description && typeof source.description === 'string') {
 25 |     field.description = source.description;
 26 |   }
 27 |   if (source?.options && typeof source.options === 'object') {
 28 |     field.options = source.options as Record<string, unknown>;
 29 |   }
 30 |   return field;
 31 | }
 32 | 
 33 | function normalizeView(raw: unknown): DescribeViewEntry {
 34 |   const source = raw as Record<string, unknown>;
 35 |   const view: DescribeViewEntry = {
 36 |     id: String(source?.id ?? ''),
 37 |     name: String(source?.name ?? '')
 38 |   };
 39 |   if (source?.type && typeof source.type === 'string') {
 40 |     view.type = source.type;
 41 |   }
 42 |   return view;
 43 | }
 44 | 
 45 | function normalizeTable(
 46 |   raw: unknown,
 47 |   { includeFields, includeViews }: { includeFields: boolean; includeViews: boolean }
 48 | ): DescribeTableEntry {
 49 |   const source = raw as Record<string, unknown>;
 50 |   const table: DescribeTableEntry = {
 51 |     id: String(source?.id ?? ''),
 52 |     name: String(source?.name ?? '')
 53 |   };
 54 |   if (source?.primaryFieldId && typeof source.primaryFieldId === 'string') {
 55 |     table.primaryFieldId = source.primaryFieldId;
 56 |   }
 57 |   if (includeFields && Array.isArray(source?.fields)) {
 58 |     table.fields = (source.fields as unknown[]).map((field) => normalizeField(field));
 59 |   }
 60 |   if (includeViews && Array.isArray(source?.views)) {
 61 |     table.views = (source.views as unknown[]).map((view) => normalizeView(view));
 62 |   }
 63 |   return table;
 64 | }
 65 | 
 66 | export function registerDescribeTool(server: McpServer, ctx: AppContext): void {
 67 |   server.registerTool(
 68 |     'describe',
 69 |     {
 70 |       description: 'Describe Airtable base or table schema.',
 71 |       inputSchema: describeInputShape,
 72 |       outputSchema: describeOutputSchema.shape
 73 |     },
 74 |     async (args: DescribeInput, _extra: unknown) => {
 75 |       try {
 76 |         const input = describeInputSchema.parse(args);
 77 |         ctx.governance.ensureOperationAllowed('describe');
 78 |         ctx.governance.ensureBaseAllowed(input.baseId);
 79 | 
 80 |         const includeFields = input.includeFields ?? true;
 81 |         const includeViews = input.includeViews ?? false;
 82 | 
 83 |         const logger = ctx.logger.child({
 84 |           tool: 'describe',
 85 |           baseId: input.baseId,
 86 |           scope: input.scope
 87 |         });
 88 | 
 89 |         const [baseInfo, tableInfo] = await Promise.all([
 90 |           ctx.airtable.getBase(input.baseId),
 91 |           ctx.airtable.listTables(input.baseId)
 92 |         ]);
 93 | 
 94 |         const baseName =
 95 |           typeof (baseInfo as any)?.name === 'string'
 96 |             ? String((baseInfo as any).name)
 97 |             : input.baseId;
 98 | 
 99 |         const rawTables: unknown[] = Array.isArray((tableInfo as any)?.tables)
100 |           ? ((tableInfo as any).tables as unknown[])
101 |           : [];
102 | 
103 |         const tables: DescribeTableEntry[] = rawTables
104 |           .filter((rawTable: unknown) => {
105 |             const record = rawTable as Record<string, unknown>;
106 |             const tableId = typeof record.id === 'string' ? record.id : '';
107 |             const tableName = typeof record.name === 'string' ? record.name : '';
108 |             const idAllowed = tableId
109 |               ? ctx.governance.isTableAllowed(input.baseId, tableId)
110 |               : false;
111 |             const nameAllowed = tableName
112 |               ? ctx.governance.isTableAllowed(input.baseId, tableName)
113 |               : false;
114 |             return idAllowed || nameAllowed;
115 |           })
116 |           .map((table: unknown) => normalizeTable(table, { includeFields, includeViews }));
117 | 
118 |         let selectedTables: DescribeTableEntry[] = tables;
119 | 
120 |         if (input.scope === 'table') {
121 |           const target = tables.find(
122 |             (tableRecord) =>
123 |               String(tableRecord.id) === input.table ||
124 |               String(tableRecord.name).toLowerCase() === input.table?.toLowerCase()
125 |           );
126 |           if (!target) {
127 |             const context: Record<string, string> = { baseId: input.baseId };
128 |             if (input.table) {
129 |               context.table = input.table;
130 |             }
131 |             throw new NotFoundError(`Table ${input.table} not found in base ${input.baseId}`, {
132 |               context
133 |             });
134 |           }
135 |           const targetId = String(target.id);
136 |           const targetName = String(target.name);
137 |           if (
138 |             !ctx.governance.isTableAllowed(input.baseId, targetId) &&
139 |             !ctx.governance.isTableAllowed(input.baseId, targetName)
140 |           ) {
141 |             const context: Record<string, string> = { baseId: input.baseId };
142 |             if (input.table) {
143 |               context.table = input.table;
144 |             }
145 |             throw new GovernanceError(`Table ${input.table} is not allowed in base ${input.baseId}`, {
146 |               context
147 |             });
148 |           }
149 |           selectedTables = [target];
150 |         }
151 | 
152 |         const structuredContent: DescribeOutput = {
153 |           base: {
154 |             id: input.baseId,
155 |             name: baseName
156 |           },
157 |           tables: selectedTables
158 |         };
159 | 
160 |         if (input.scope === 'base' && includeViews) {
161 |           structuredContent.views = rawTables
162 |             .flatMap((table: unknown) => {
163 |               const record = table as Record<string, unknown>;
164 |               return Array.isArray(record.views) ? (record.views as unknown[]) : [];
165 |             })
166 |             .map((view: unknown) => normalizeView(view));
167 |         }
168 | 
169 |         logger.debug('Describe completed', {
170 |           tableCount: selectedTables.length
171 |         });
172 | 
173 |         return {
174 |           structuredContent,
175 |           content: [] as const
176 |         };
177 |       } catch (error) {
178 |         return handleToolError('describe', error, ctx);
179 |       }
180 |     }
181 |   );
182 | }
183 | 
```

--------------------------------------------------------------------------------
/tests/test_all_features.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | 
  3 | echo "🎯 COMPREHENSIVE TEST - AIRTABLE MCP v1.4.0"
  4 | echo "==========================================="
  5 | echo ""
  6 | 
  7 | PASSED=0
  8 | FAILED=0
  9 | TOTAL=0
 10 | 
 11 | # Test function
 12 | test_feature() {
 13 |     local name=$1
 14 |     local result=$2
 15 |     ((TOTAL++))
 16 |     
 17 |     if [ "$result" = "PASS" ]; then
 18 |         echo "✅ $name"
 19 |         ((PASSED++))
 20 |     else
 21 |         echo "❌ $name"
 22 |         ((FAILED++))
 23 |     fi
 24 | }
 25 | 
 26 | echo "📊 TESTING ALL 12 TOOLS"
 27 | echo "======================="
 28 | echo ""
 29 | 
 30 | # 1. List tables
 31 | result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 32 |     -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "list_tables"}}')
 33 | if [[ "$result" == *"table"* ]]; then
 34 |     test_feature "list_tables" "PASS"
 35 | else
 36 |     test_feature "list_tables" "FAIL"
 37 | fi
 38 | 
 39 | # 2. Create record
 40 | result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 41 |     -d '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "create_record", "arguments": {"table": "tblH7TnJxYpNqhQYK", "fields": {"Name": "Final Test", "Status": "Active"}}}}')
 42 | if [[ "$result" == *"Successfully created"* ]]; then
 43 |     test_feature "create_record" "PASS"
 44 |     RECORD_ID=$(echo "$result" | grep -o 'rec[a-zA-Z0-9]\{10,20\}' | head -1)
 45 | else
 46 |     test_feature "create_record" "FAIL"
 47 |     RECORD_ID=""
 48 | fi
 49 | 
 50 | # 3. Get record
 51 | if [ ! -z "$RECORD_ID" ]; then
 52 |     result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 53 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 3, \"method\": \"tools/call\", \"params\": {\"name\": \"get_record\", \"arguments\": {\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\"}}}")
 54 |     [[ "$result" == *"Record $RECORD_ID"* ]] && test_feature "get_record" "PASS" || test_feature "get_record" "FAIL"
 55 | else
 56 |     test_feature "get_record" "SKIP"
 57 | fi
 58 | 
 59 | # 4. Update record
 60 | if [ ! -z "$RECORD_ID" ]; then
 61 |     result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 62 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 4, \"method\": \"tools/call\", \"params\": {\"name\": \"update_record\", \"arguments\": {\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\", \"fields\": {\"Status\": \"Completed\"}}}}")
 63 |     [[ "$result" == *"Successfully updated"* ]] && test_feature "update_record" "PASS" || test_feature "update_record" "FAIL"
 64 | else
 65 |     test_feature "update_record" "SKIP"
 66 | fi
 67 | 
 68 | # 5. List records
 69 | result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 70 |     -d '{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "list_records", "arguments": {"table": "tblH7TnJxYpNqhQYK", "maxRecords": 3}}}')
 71 | [[ "$result" == *"record"* ]] && test_feature "list_records" "PASS" || test_feature "list_records" "FAIL"
 72 | 
 73 | # 6. Search records
 74 | result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 75 |     -d '{"jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": {"name": "search_records", "arguments": {"table": "tblH7TnJxYpNqhQYK", "maxRecords": 3}}}')
 76 | [[ "$result" == *"record"* ]] && test_feature "search_records" "PASS" || test_feature "search_records" "FAIL"
 77 | 
 78 | # 7. Delete record
 79 | if [ ! -z "$RECORD_ID" ]; then
 80 |     result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 81 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 7, \"method\": \"tools/call\", \"params\": {\"name\": \"delete_record\", \"arguments\": {\"table\": \"tblH7TnJxYpNqhQYK\", \"recordId\": \"$RECORD_ID\"}}}")
 82 |     [[ "$result" == *"Successfully deleted"* ]] && test_feature "delete_record" "PASS" || test_feature "delete_record" "FAIL"
 83 | else
 84 |     test_feature "delete_record" "SKIP"
 85 | fi
 86 | 
 87 | # 8. List webhooks
 88 | result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 89 |     -d '{"jsonrpc": "2.0", "id": 8, "method": "tools/call", "params": {"name": "list_webhooks"}}')
 90 | [[ "$result" == *"webhook"* ]] && test_feature "list_webhooks" "PASS" || test_feature "list_webhooks" "FAIL"
 91 | 
 92 | # 9. Create webhook
 93 | result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
 94 |     -d '{"jsonrpc": "2.0", "id": 9, "method": "tools/call", "params": {"name": "create_webhook", "arguments": {"notificationUrl": "https://webhook.site/test-final"}}}')
 95 | if [[ "$result" == *"Successfully created"* ]]; then
 96 |     test_feature "create_webhook" "PASS"
 97 |     WEBHOOK_ID=$(echo "$result" | grep -o 'ach[a-zA-Z0-9]*' | head -1)
 98 | else
 99 |     test_feature "create_webhook" "FAIL"
100 |     WEBHOOK_ID=""
101 | fi
102 | 
103 | # 10. Get webhook payloads
104 | if [ ! -z "$WEBHOOK_ID" ]; then
105 |     result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
106 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 10, \"method\": \"tools/call\", \"params\": {\"name\": \"get_webhook_payloads\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
107 |     [[ "$result" == *"payload"* ]] && test_feature "get_webhook_payloads" "PASS" || test_feature "get_webhook_payloads" "FAIL"
108 | else
109 |     test_feature "get_webhook_payloads" "SKIP"
110 | fi
111 | 
112 | # 11. Refresh webhook
113 | if [ ! -z "$WEBHOOK_ID" ]; then
114 |     result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
115 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 11, \"method\": \"tools/call\", \"params\": {\"name\": \"refresh_webhook\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
116 |     [[ "$result" == *"refreshed"* ]] && test_feature "refresh_webhook" "PASS" || test_feature "refresh_webhook" "FAIL"
117 | else
118 |     test_feature "refresh_webhook" "SKIP"
119 | fi
120 | 
121 | # 12. Delete webhook
122 | if [ ! -z "$WEBHOOK_ID" ]; then
123 |     result=$(curl -s -X POST http://localhost:8010/mcp -H "Content-Type: application/json" \
124 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 12, \"method\": \"tools/call\", \"params\": {\"name\": \"delete_webhook\", \"arguments\": {\"webhookId\": \"$WEBHOOK_ID\"}}}")
125 |     [[ "$result" == *"deleted"* ]] && test_feature "delete_webhook" "PASS" || test_feature "delete_webhook" "FAIL"
126 | else
127 |     test_feature "delete_webhook" "SKIP"
128 | fi
129 | 
130 | echo ""
131 | echo "📈 FINAL RESULTS"
132 | echo "==============="
133 | echo "Total Tests: $TOTAL"
134 | echo "✅ Passed: $PASSED"
135 | echo "❌ Failed: $FAILED"
136 | echo "Success Rate: $(( PASSED * 100 / TOTAL ))%"
137 | 
138 | if [ $FAILED -eq 0 ]; then
139 |     echo ""
140 |     echo "🎉 ALL TESTS PASSED! v1.4.0 is ready for production!"
141 |     exit 0
142 | else
143 |     echo ""
144 |     echo "⚠️  $FAILED test(s) failed. Please review."
145 |     exit 1
146 | fi
```

--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------

```markdown
  1 | # 🚀 Pull Request - Trust Score 100/100
  2 | 
  3 | <!-- 
  4 | Thank you for contributing to the Airtable MCP Server! 
  5 | Your contribution helps us achieve our goal of a perfect 100/100 Trust Score.
  6 | -->
  7 | 
  8 | ## 📋 PR Information
  9 | 
 10 | **PR Type**: <!-- Check all that apply -->
 11 | - [ ] 🐛 Bug Fix
 12 | - [ ] ✨ New Feature  
 13 | - [ ] 🔒 Security Enhancement
 14 | - [ ] 📚 Documentation Update
 15 | - [ ] 🧹 Code Refactoring
 16 | - [ ] ⚡ Performance Improvement
 17 | - [ ] 🧪 Test Enhancement
 18 | - [ ] 🔧 Build/CI Changes
 19 | - [ ] 💥 Breaking Change
 20 | 
 21 | **Issue Reference**: 
 22 | <!-- Link to the issue this PR addresses -->
 23 | - Closes #[issue_number]
 24 | - Related to #[issue_number]
 25 | 
 26 | ## 📝 Description
 27 | 
 28 | ### What Changed
 29 | <!-- Provide a clear and concise description of what this PR does -->
 30 | 
 31 | ### Why This Change
 32 | <!-- Explain the motivation behind this change -->
 33 | 
 34 | ### How It Works
 35 | <!-- Describe the technical approach and implementation -->
 36 | 
 37 | ## 🎯 Trust Score Impact
 38 | 
 39 | **Trust Score Categories Affected**: <!-- Check all that apply -->
 40 | - [ ] 🛡️ Security & Authentication
 41 | - [ ] 📊 Code Quality & Standards
 42 | - [ ] 🧪 Testing & Reliability
 43 | - [ ] 📚 Documentation & Usability
 44 | - [ ] 🚀 Performance & Scalability
 45 | - [ ] 🔧 CI/CD & Automation
 46 | - [ ] 🌐 Protocol Compliance
 47 | - [ ] 👥 Community & Support
 48 | 
 49 | **Expected Impact**: 
 50 | <!-- Describe how this contributes to our 100/100 Trust Score goal -->
 51 | 
 52 | ## 🧪 Testing Checklist
 53 | 
 54 | ### Automated Tests
 55 | - [ ] Unit tests added/updated
 56 | - [ ] Integration tests added/updated
 57 | - [ ] Security tests added/updated
 58 | - [ ] Performance tests added/updated
 59 | - [ ] All existing tests pass
 60 | - [ ] Coverage maintained or improved
 61 | 
 62 | ### Manual Testing
 63 | - [ ] MCP protocol functionality verified
 64 | - [ ] OAuth2 authentication tested (if applicable)
 65 | - [ ] Rate limiting verified (if applicable)
 66 | - [ ] Error handling tested
 67 | - [ ] Edge cases covered
 68 | - [ ] Backward compatibility confirmed
 69 | 
 70 | ### Test Environment
 71 | **Tested On**:
 72 | - [ ] Node.js 16.x
 73 | - [ ] Node.js 18.x  
 74 | - [ ] Node.js 20.x
 75 | - [ ] Docker container
 76 | - [ ] Multiple operating systems
 77 | 
 78 | **MCP Clients Tested**:
 79 | - [ ] Claude Desktop
 80 | - [ ] Cursor IDE
 81 | - [ ] VS Code with Cline
 82 | - [ ] Custom MCP client
 83 | 
 84 | ## 🔒 Security Review
 85 | 
 86 | ### Security Checklist
 87 | - [ ] No hardcoded secrets or credentials
 88 | - [ ] Input validation implemented
 89 | - [ ] Output sanitization applied
 90 | - [ ] Authentication/authorization checked
 91 | - [ ] SQL injection prevention verified
 92 | - [ ] XSS prevention implemented
 93 | - [ ] CSRF protection maintained
 94 | - [ ] Rate limiting respected
 95 | - [ ] Error messages don't leak sensitive info
 96 | - [ ] Dependencies updated and secure
 97 | 
 98 | ### Security Impact Assessment
 99 | <!-- If this PR has security implications, describe them -->
100 | - **Authentication Changes**: 
101 | - **Data Access Changes**: 
102 | - **New Attack Vectors**: 
103 | - **Mitigation Measures**: 
104 | 
105 | ## 📊 Performance Impact
106 | 
107 | ### Performance Checklist
108 | - [ ] No significant performance regression
109 | - [ ] Memory usage optimized
110 | - [ ] Database queries optimized (if applicable)
111 | - [ ] Network requests minimized
112 | - [ ] Caching implemented where appropriate
113 | - [ ] Async/await used properly
114 | 
115 | ### Benchmarks
116 | <!-- If applicable, include performance measurements -->
117 | **Before**:
118 | ```
119 | Metric: [value]
120 | ```
121 | 
122 | **After**:
123 | ```
124 | Metric: [value]
125 | ```
126 | 
127 | ## 📚 Documentation
128 | 
129 | ### Documentation Updates
130 | - [ ] README.md updated
131 | - [ ] API documentation updated
132 | - [ ] Code comments added/updated
133 | - [ ] Examples updated
134 | - [ ] Troubleshooting guide updated
135 | - [ ] CHANGELOG.md updated
136 | - [ ] Migration guide provided (for breaking changes)
137 | 
138 | ### Documentation Quality
139 | - [ ] Clear and concise explanations
140 | - [ ] Code examples provided
141 | - [ ] Screenshots/diagrams included (if applicable)
142 | - [ ] Links verified and working
143 | 
144 | ## 🔄 Breaking Changes
145 | 
146 | ### Breaking Change Assessment
147 | - [ ] This is NOT a breaking change
148 | - [ ] This is a breaking change (explain below)
149 | 
150 | <!-- If breaking change, provide details -->
151 | **Breaking Changes**:
152 | - **What breaks**: 
153 | - **Migration path**: 
154 | - **Deprecation timeline**: 
155 | 
156 | ## 🎬 Demo/Examples
157 | 
158 | ### How to Test This PR
159 | ```bash
160 | # Step-by-step instructions to test this PR
161 | git checkout [branch-name]
162 | npm install
163 | # ... additional setup steps
164 | ```
165 | 
166 | ### Usage Examples
167 | ```javascript
168 | // Provide code examples showing the new functionality
169 | ```
170 | 
171 | ## 📋 Review Checklist
172 | 
173 | ### Code Quality
174 | - [ ] Code follows project style guidelines
175 | - [ ] No console.log or debug statements
176 | - [ ] Error handling is comprehensive
177 | - [ ] Code is well-commented
178 | - [ ] Functions are properly documented
179 | - [ ] Variable names are descriptive
180 | - [ ] Magic numbers avoided
181 | 
182 | ### Git History
183 | - [ ] Commit messages are clear and descriptive
184 | - [ ] Commits are logically organized
185 | - [ ] No merge commits (rebased if needed)
186 | - [ ] No sensitive information in commit history
187 | 
188 | ## 🤝 Collaboration
189 | 
190 | ### Review Requests
191 | **Reviewers Needed**:
192 | - [ ] Security review required
193 | - [ ] Performance review required
194 | - [ ] Documentation review required
195 | - [ ] UI/UX review required
196 | 
197 | **Specific Review Areas**:
198 | <!-- Ask reviewers to focus on specific aspects -->
199 | - Please review the OAuth2 implementation for security
200 | - Please check the new API endpoints for usability
201 | - Please verify the documentation is clear
202 | 
203 | ### Follow-up Tasks
204 | <!-- List any follow-up work needed -->
205 | - [ ] Create/update related issues
206 | - [ ] Plan future enhancements
207 | - [ ] Update project roadmap
208 | - [ ] Coordinate with documentation team
209 | 
210 | ## 🎯 Success Criteria
211 | 
212 | ### Definition of Done
213 | - [ ] All acceptance criteria met
214 | - [ ] All tests passing
215 | - [ ] Security review completed
216 | - [ ] Documentation updated
217 | - [ ] Performance impact assessed
218 | - [ ] Backward compatibility verified
219 | - [ ] CI/CD pipeline passing
220 | 
221 | ### Trust Score Validation
222 | - [ ] Contributes to security improvements
223 | - [ ] Maintains or improves code quality
224 | - [ ] Includes comprehensive testing
225 | - [ ] Provides clear documentation
226 | - [ ] Follows community best practices
227 | 
228 | ## 📸 Screenshots/Media
229 | 
230 | <!-- Include screenshots, GIFs, or videos demonstrating the changes -->
231 | 
232 | ## 🙏 Acknowledgments
233 | 
234 | <!-- Thank contributors, mention inspiration, or credit sources -->
235 | 
236 | ---
237 | 
238 | ## 📞 Need Help?
239 | 
240 | - 💬 **Questions**: Start a [discussion](https://github.com/rashidazarang/airtable-mcp/discussions)
241 | - 🐛 **Issues**: Check our [issue tracker](https://github.com/rashidazarang/airtable-mcp/issues)
242 | - 📚 **Docs**: Read our [documentation](./README.md)
243 | - 🔒 **Security**: Email security@[domain] for private matters
244 | 
245 | **🎯 Our Mission**: Building the most trusted and comprehensive MCP server for Airtable with a perfect **100/100 Trust Score**. Thank you for contributing to this goal! 🚀
```

--------------------------------------------------------------------------------
/RELEASE_SUMMARY_v3.2.x.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Release Summary: v3.2.1 - v3.2.4
  2 | ## Major Security & Architecture Updates
  3 | 
  4 | This document summarizes all releases from v3.2.1 to v3.2.4, representing a comprehensive overhaul of the Airtable MCP server with critical security fixes and architectural improvements.
  5 | 
  6 | ---
  7 | 
  8 | ## 📦 v3.2.4 - Complete XSS Security Fix
  9 | **Released:** September 9, 2025  
 10 | **Type:** 🔒 Security Release  
 11 | **GitHub Alerts:** #10 & #11 Resolved
 12 | 
 13 | ### What's Fixed
 14 | - **XSS Vulnerabilities** in OAuth2 endpoint (`airtable_simple_production.js:708-710`)
 15 |   - ✅ Unicode escaping for all special characters in JSON
 16 |   - ✅ Using `textContent` instead of `innerHTML` for dynamic content
 17 |   - ✅ Multiple layers of character escaping
 18 |   - ✅ Defense-in-depth XSS prevention
 19 | 
 20 | ### Technical Details
 21 | ```javascript
 22 | // Before (Vulnerable)
 23 | var config = ${JSON.stringify(data)};
 24 | <p>Client ID: ${clientId}</p>
 25 | 
 26 | // After (Secure)
 27 | var config = ${safeJsonConfig}; // Unicode-escaped
 28 | document.getElementById('client-id').textContent = clientId;
 29 | ```
 30 | 
 31 | ---
 32 | 
 33 | ## 📦 v3.2.3 - Command Injection Complete Fix
 34 | **Released:** September 9, 2025  
 35 | **Type:** 🔒 Security Release  
 36 | **GitHub Alert:** #10 (Python) Resolved
 37 | 
 38 | ### What's Fixed
 39 | - **Command Injection** in Python test client (`test_client.py`)
 40 |   - ✅ BASE_ID validation at startup
 41 |   - ✅ Eliminated string interpolation vulnerabilities
 42 |   - ✅ Path traversal protection
 43 |   - ✅ Token format validation
 44 |   - ✅ Complete input sanitization
 45 | 
 46 | ### Security Improvements
 47 | ```python
 48 | # Before (Vulnerable)
 49 | result = api_call(f"meta/bases/{BASE_ID}/tables")
 50 | 
 51 | # After (Secure)
 52 | # BASE_ID validated at startup
 53 | if not all(c.isalnum() or c in '-_' for c in BASE_ID):
 54 |     print(f"Error: Invalid BASE_ID format")
 55 |     sys.exit(1)
 56 | endpoint = "meta/bases/" + BASE_ID + "/tables"
 57 | ```
 58 | 
 59 | ---
 60 | 
 61 | ## 📦 v3.2.2 - Initial Security Patches
 62 | **Released:** September 9, 2025  
 63 | **Type:** 🔒 Security Release  
 64 | **GitHub Alert:** #10 Partial Fix
 65 | 
 66 | ### What's Fixed
 67 | - **Initial command injection fixes** in `test_client.py`
 68 |   - ✅ Added input validation for API endpoints
 69 |   - ✅ Removed unused subprocess import
 70 |   - ✅ Basic endpoint sanitization
 71 | 
 72 | ### Note
 73 | This was a partial fix. Complete resolution came in v3.2.3.
 74 | 
 75 | ---
 76 | 
 77 | ## 📦 v3.2.1 - TypeScript Architecture Fix & Project Restructure
 78 | **Released:** September 9, 2025  
 79 | **Type:** 🏗️ Major Architecture Update
 80 | 
 81 | ### Critical Fix
 82 | - **TypeScript Compilation Issue** completely resolved
 83 |   - ✅ Fixed `.d.ts` files containing runtime code
 84 |   - ✅ Proper separation of types and implementation
 85 | 
 86 | ### New Files Created
 87 | ```
 88 | src/typescript/
 89 | ├── errors.ts           # Runtime error classes
 90 | ├── tools-schemas.ts    # Tool schema constants
 91 | └── prompt-templates.ts # AI prompt templates
 92 | ```
 93 | 
 94 | ### Project Restructure
 95 | ```
 96 | airtable-mcp/
 97 | ├── src/
 98 | │   ├── index.js           # Main entry point
 99 | │   ├── typescript/        # TypeScript implementation
100 | │   ├── javascript/        # JavaScript implementation
101 | │   └── python/           # Python implementation
102 | ├── dist/                 # Compiled output
103 | ├── docs/
104 | │   ├── guides/          # User guides
105 | │   └── releases/        # Release notes
106 | ├── tests/               # All test files
107 | └── types/               # TypeScript definitions
108 | ```
109 | 
110 | ### What Changed
111 | - ✅ World-class project organization
112 | - ✅ TypeScript now compiles successfully
113 | - ✅ Proper build system with npm scripts
114 | - ✅ ESLint and Prettier configurations
115 | - ✅ Jest testing framework setup
116 | - ✅ CI/CD pipeline structure
117 | 
118 | ---
119 | 
120 | ## 🎯 Combined Impact
121 | 
122 | ### Security Fixes Summary
123 | | Alert | Type | File | Version | Status |
124 | |-------|------|------|---------|---------|
125 | | #10 | XSS | `airtable_simple_production.js:708` | v3.2.4 | ✅ Fixed |
126 | | #11 | XSS | `airtable_simple_production.js:710` | v3.2.4 | ✅ Fixed |
127 | | #10 | Command Injection | `test_client.py` | v3.2.3 | ✅ Fixed |
128 | 
129 | ### Architecture Improvements
130 | - ✅ TypeScript compilation working
131 | - ✅ Proper file organization
132 | - ✅ Clean separation of concerns
133 | - ✅ Professional build system
134 | - ✅ Comprehensive testing setup
135 | 
136 | ### Backwards Compatibility
137 | ✅ **No breaking changes** across all versions
138 | - All existing functionality preserved
139 | - API endpoints unchanged
140 | - Both JS and TS implementations working
141 | 
142 | ---
143 | 
144 | ## 📥 Installation
145 | 
146 | ### New Installation
147 | ```bash
148 | npm install @rashidazarang/[email protected]
149 | ```
150 | 
151 | ### Update from Any Previous Version
152 | ```bash
153 | npm update @rashidazarang/airtable-mcp
154 | ```
155 | 
156 | ### Verify Installation
157 | ```bash
158 | npm list @rashidazarang/airtable-mcp
159 | # Should show: @rashidazarang/[email protected]
160 | ```
161 | 
162 | ---
163 | 
164 | ## 🚀 Quick Start
165 | 
166 | ### JavaScript
167 | ```bash
168 | AIRTABLE_TOKEN=your_token AIRTABLE_BASE_ID=your_base \
169 |   node node_modules/@rashidazarang/airtable-mcp/src/javascript/airtable_simple_production.js
170 | ```
171 | 
172 | ### TypeScript
173 | ```bash
174 | # Build first
175 | npm run build
176 | 
177 | # Then run
178 | AIRTABLE_TOKEN=your_token AIRTABLE_BASE_ID=your_base \
179 |   node node_modules/@rashidazarang/airtable-mcp/dist/typescript/airtable-mcp-server.js
180 | ```
181 | 
182 | ---
183 | 
184 | ## 📋 Migration Guide
185 | 
186 | ### From v3.0.x or earlier
187 | 1. Update to v3.2.4: `npm update @rashidazarang/airtable-mcp`
188 | 2. If using TypeScript, rebuild: `npm run build`
189 | 3. No code changes required
190 | 
191 | ### From v3.1.x
192 | 1. Update to v3.2.4: `npm update @rashidazarang/airtable-mcp`
193 | 2. No changes required - security patches only
194 | 
195 | ### From v3.2.1-3.2.3
196 | 1. Update to v3.2.4: `npm update @rashidazarang/airtable-mcp`
197 | 2. Get latest security fixes
198 | 
199 | ---
200 | 
201 | ## ⚠️ Important Security Notice
202 | 
203 | **All users should update to v3.2.4 immediately** to get:
204 | - Complete XSS protection in OAuth2 flows
205 | - Full command injection prevention
206 | - Path traversal protection
207 | - Comprehensive input validation
208 | 
209 | ---
210 | 
211 | ## 📊 Version Comparison
212 | 
213 | | Feature | v3.2.1 | v3.2.2 | v3.2.3 | v3.2.4 |
214 | |---------|--------|--------|--------|--------|
215 | | TypeScript Compilation | ✅ Fixed | ✅ | ✅ | ✅ |
216 | | Project Structure | ✅ New | ✅ | ✅ | ✅ |
217 | | Command Injection Fix | ❌ | ⚠️ Partial | ✅ Complete | ✅ |
218 | | XSS Protection | ❌ | ❌ | ❌ | ✅ Complete |
219 | | Production Ready | ✅ | ✅ | ✅ | ✅ |
220 | 
221 | ---
222 | 
223 | ## 🙏 Acknowledgments
224 | 
225 | - GitHub Security Scanning for identifying vulnerabilities
226 | - Community for patience during rapid security updates
227 | - Contributors to the TypeScript architecture improvements
228 | 
229 | ---
230 | 
231 | ## 📚 Resources
232 | 
233 | - **Repository:** https://github.com/rashidazarang/airtable-mcp
234 | - **Issues:** https://github.com/rashidazarang/airtable-mcp/issues
235 | - **NPM:** https://www.npmjs.com/package/@rashidazarang/airtable-mcp
236 | - **Changelog:** [CHANGELOG.md](./CHANGELOG.md)
237 | 
238 | ---
239 | 
240 | **Current Version: v3.2.4**  
241 | **Status: Fully Secure & Production Ready**  
242 | **Last Updated: September 9, 2025**
```

--------------------------------------------------------------------------------
/examples/airtable-crud-example.js:
--------------------------------------------------------------------------------

```javascript
  1 | /**
  2 |  * Example script demonstrating how to use the Airtable CRUD utilities
  3 |  */
  4 | const dotenv = require('dotenv');
  5 | const baseUtils = require('../tools/airtable-base');
  6 | const crudUtils = require('../tools/airtable-crud');
  7 | const schemaUtils = require('../tools/airtable-schema');
  8 | 
  9 | // Load environment variables
 10 | dotenv.config();
 11 | 
 12 | // Configuration
 13 | const EXAMPLE_TABLE_NAME = 'Example Tasks';
 14 | const EXAMPLE_RECORDS = [
 15 |   { 
 16 |     Name: 'Complete project documentation', 
 17 |     Description: 'Write comprehensive documentation for the project', 
 18 |     Status: 'Not Started', 
 19 |     Priority: 'High',
 20 |     DueDate: '2023-12-31'
 21 |   },
 22 |   { 
 23 |     Name: 'Fix login bug', 
 24 |     Description: 'Users are experiencing issues with the login process', 
 25 |     Status: 'In Progress', 
 26 |     Priority: 'Critical',
 27 |     DueDate: '2023-11-15'
 28 |   },
 29 |   { 
 30 |     Name: 'Add new feature', 
 31 |     Description: 'Implement the new feature requested by the client', 
 32 |     Status: 'Not Started', 
 33 |     Priority: 'Medium',
 34 |     DueDate: '2024-01-15'
 35 |   }
 36 | ];
 37 | 
 38 | /**
 39 |  * Main function to run the example
 40 |  */
 41 | async function runExample() {
 42 |   console.log('Starting Airtable CRUD Example...\n');
 43 |   
 44 |   const baseId = process.env.AIRTABLE_BASE_ID;
 45 |   if (!baseId) {
 46 |     console.error('AIRTABLE_BASE_ID not set in .env file');
 47 |     process.exit(1);
 48 |   }
 49 |   
 50 |   try {
 51 |     // Step 1: Check if we have access to the base
 52 |     console.log('Step 1: Checking base access...');
 53 |     const bases = await baseUtils.listAllBases();
 54 |     const hasAccess = bases.some(base => base.id === baseId);
 55 |     
 56 |     if (!hasAccess) {
 57 |       throw new Error(`No access to base with ID: ${baseId}`);
 58 |     }
 59 |     
 60 |     console.log(`✅ Access confirmed to base: ${baseId}\n`);
 61 |     
 62 |     // Step 2: List existing tables
 63 |     console.log('Step 2: Listing existing tables...');
 64 |     const tables = await baseUtils.listTables(baseId);
 65 |     console.log(`Found ${tables.length} tables in the base:`);
 66 |     tables.forEach(table => console.log(`- ${table.name}`));
 67 |     console.log();
 68 |     
 69 |     // Step 3: Check if our example table exists
 70 |     console.log('Step 3: Checking if example table exists...');
 71 |     let tableExists = await crudUtils.tableExists(baseId, EXAMPLE_TABLE_NAME);
 72 |     
 73 |     if (tableExists) {
 74 |       console.log(`Table "${EXAMPLE_TABLE_NAME}" already exists\n`);
 75 |     } else {
 76 |       console.log(`Table "${EXAMPLE_TABLE_NAME}" does not exist, creating it...\n`);
 77 |       
 78 |       // Step 4: Create the example table
 79 |       console.log('Step 4: Creating example table...');
 80 |       const tableConfig = {
 81 |         name: EXAMPLE_TABLE_NAME,
 82 |         description: 'Example table for demonstrating CRUD operations',
 83 |         fields: [
 84 |           {
 85 |             name: 'Name',
 86 |             type: 'singleLineText',
 87 |             description: 'Task name'
 88 |           },
 89 |           {
 90 |             name: 'Description',
 91 |             type: 'multilineText',
 92 |             description: 'Task description'
 93 |           },
 94 |           {
 95 |             name: 'Status',
 96 |             type: 'singleSelect',
 97 |             options: {
 98 |               choices: [
 99 |                 { name: 'Not Started' },
100 |                 { name: 'In Progress' },
101 |                 { name: 'Completed' }
102 |               ]
103 |             },
104 |             description: 'Current status of the task'
105 |           },
106 |           {
107 |             name: 'Priority',
108 |             type: 'singleSelect',
109 |             options: {
110 |               choices: [
111 |                 { name: 'Low' },
112 |                 { name: 'Medium' },
113 |                 { name: 'High' },
114 |                 { name: 'Critical' }
115 |               ]
116 |             },
117 |             description: 'Task priority'
118 |           },
119 |           {
120 |             name: 'DueDate',
121 |             type: 'date',
122 |             description: 'When the task is due',
123 |             options: {
124 |               dateFormat: {
125 |                 name: 'local'
126 |               }
127 |             }
128 |           }
129 |         ]
130 |       };
131 |       
132 |       await schemaUtils.createTable(baseId, tableConfig);
133 |       console.log(`✅ Created table: ${EXAMPLE_TABLE_NAME}\n`);
134 |     }
135 |     
136 |     // Step 5: Create records
137 |     console.log('Step 5: Creating example records...');
138 |     const createdRecords = await crudUtils.createRecords(baseId, EXAMPLE_TABLE_NAME, EXAMPLE_RECORDS);
139 |     console.log(`✅ Created ${createdRecords.length} records\n`);
140 |     
141 |     // Step 6: Read all records
142 |     console.log('Step 6: Reading all records...');
143 |     const allRecords = await crudUtils.readRecords(baseId, EXAMPLE_TABLE_NAME, 100);
144 |     console.log(`✅ Read ${allRecords.length} records`);
145 |     console.log('Sample record:');
146 |     console.log(JSON.stringify(allRecords[0], null, 2));
147 |     console.log();
148 |     
149 |     // Step 7: Filter records
150 |     console.log('Step 7: Filtering records by status...');
151 |     const notStartedRecords = await crudUtils.readRecords(
152 |       baseId, 
153 |       EXAMPLE_TABLE_NAME, 
154 |       100, 
155 |       'Status="Not Started"'
156 |     );
157 |     console.log(`✅ Found ${notStartedRecords.length} records with Status="Not Started"`);
158 |     notStartedRecords.forEach(record => console.log(`- ${record.Name} (Priority: ${record.Priority})`));
159 |     console.log();
160 |     
161 |     // Step 8: Update records
162 |     console.log('Step 8: Updating records...');
163 |     const recordsToUpdate = notStartedRecords.map(record => ({
164 |       id: record.id,
165 |       fields: { Status: 'In Progress' }
166 |     }));
167 |     
168 |     const updatedRecords = await crudUtils.updateRecords(baseId, EXAMPLE_TABLE_NAME, recordsToUpdate);
169 |     console.log(`✅ Updated ${updatedRecords.length} records to Status="In Progress"\n`);
170 |     
171 |     // Step 9: Verify updates
172 |     console.log('Step 9: Verifying updates...');
173 |     const inProgressRecords = await crudUtils.readRecords(
174 |       baseId, 
175 |       EXAMPLE_TABLE_NAME, 
176 |       100, 
177 |       'Status="In Progress"'
178 |     );
179 |     console.log(`✅ Found ${inProgressRecords.length} records with Status="In Progress"`);
180 |     inProgressRecords.forEach(record => console.log(`- ${record.Name} (Priority: ${record.Priority})`));
181 |     console.log();
182 |     
183 |     // Step 10: Delete records (optional - commented out to preserve data)
184 |     console.log('Step 10: Deleting records (optional)...');
185 |     console.log('Skipping deletion to preserve example data.');
186 |     console.log('To delete records, uncomment the code below:');
187 |     console.log('```');
188 |     console.log('const recordIdsToDelete = allRecords.map(record => record.id);');
189 |     console.log('const deletedRecords = await crudUtils.deleteRecords(baseId, EXAMPLE_TABLE_NAME, recordIdsToDelete);');
190 |     console.log('console.log(`✅ Deleted ${deletedRecords.length} records`);');
191 |     console.log('```\n');
192 |     
193 |     console.log('Example completed successfully!');
194 |     console.log('You can now view the data in your Airtable base.');
195 |     
196 |   } catch (error) {
197 |     console.error('Error during example:', error.message);
198 |     process.exit(1);
199 |   }
200 | }
201 | 
202 | // Run the example
203 | runExample(); 
```

--------------------------------------------------------------------------------
/docs/releases/RELEASE_NOTES_v1.5.0.md:
--------------------------------------------------------------------------------

```markdown
  1 | # 🚀 Airtable MCP Server v1.5.0 Release Notes
  2 | 
  3 | **Release Date**: August 15, 2025  
  4 | **Major Update**: Enhanced Schema Management & Advanced Features
  5 | 
  6 | ## 🎯 Overview
  7 | 
  8 | Version 1.5.0 represents a **major expansion** of the Airtable MCP Server, adding comprehensive schema management capabilities inspired by the best features from domdomegg's airtable-mcp-server while maintaining our unique webhook support. This release **doubles** the number of available tools from 12 to **23 tools**.
  9 | 
 10 | ## ✨ New Features
 11 | 
 12 | ### 📊 Schema Discovery Tools (6 New Tools)
 13 | 
 14 | 1. **`list_bases`** - Discover all accessible Airtable bases
 15 |    - Lists all bases with permissions
 16 |    - Supports pagination with offset parameter
 17 |    - Shows base names, IDs, and permission levels
 18 | 
 19 | 2. **`get_base_schema`** - Complete base schema information
 20 |    - Detailed table structures and relationships
 21 |    - Field definitions with types and options
 22 |    - View configurations and metadata
 23 | 
 24 | 3. **`describe_table`** - Enhanced table inspection
 25 |    - Comprehensive field information including IDs, types, descriptions
 26 |    - View details and configurations
 27 |    - Much more detailed than the basic `list_tables`
 28 | 
 29 | 4. **`list_field_types`** - Field type reference
 30 |    - Complete documentation of all Airtable field types
 31 |    - Includes basic fields (text, number, date) and advanced fields (formulas, lookups)
 32 |    - Helpful for understanding what field types are available for creation
 33 | 
 34 | 5. **`get_table_views`** - View management
 35 |    - Lists all views for a specific table
 36 |    - Shows view types, IDs, and configurations
 37 |    - Includes visible field information
 38 | 
 39 | ### 🏗️ Table Management Tools (3 New Tools)
 40 | 
 41 | 6. **`create_table`** - Programmatic table creation
 42 |    - Create new tables with custom field definitions
 43 |    - Support for all field types with proper validation
 44 |    - Optional table descriptions
 45 | 
 46 | 7. **`update_table`** - Table metadata modification
 47 |    - Update table names and descriptions
 48 |    - Non-destructive metadata changes
 49 | 
 50 | 8. **`delete_table`** - Table removal (with safety checks)
 51 |    - Requires explicit confirmation with `confirm=true`
 52 |    - Permanently removes table and all data
 53 |    - Safety warnings to prevent accidental deletions
 54 | 
 55 | ### 🔧 Field Management Tools (4 New Tools)
 56 | 
 57 | 9. **`create_field`** - Add fields to existing tables
 58 |    - Support for all Airtable field types
 59 |    - Custom field options and descriptions
 60 |    - Validates field types and configurations
 61 | 
 62 | 10. **`update_field`** - Modify existing field properties
 63 |     - Update field names, descriptions, and options
 64 |     - Change field configurations safely
 65 | 
 66 | 11. **`delete_field`** - Remove fields (with safety checks)
 67 |     - Requires explicit confirmation with `confirm=true`
 68 |     - Permanently removes field and all data
 69 |     - Safety warnings to prevent accidental deletions
 70 | 
 71 | ## 🔄 Enhanced Existing Features
 72 | 
 73 | - **Improved error handling** for all metadata operations
 74 | - **Better table/field lookup** supporting both names and IDs
 75 | - **Enhanced validation** for destructive operations
 76 | - **Consistent response formatting** across all tools
 77 | 
 78 | ## 📊 Tool Count Summary
 79 | 
 80 | | Category | v1.4.0 | v1.5.0 | New in v1.5.0 |
 81 | |----------|--------|--------|----------------|
 82 | | **Data Operations** | 7 | 7 | - |
 83 | | **Webhook Management** | 5 | 5 | - |
 84 | | **Schema Management** | 0 | 11 | ✅ 11 new tools |
 85 | | **Total Tools** | **12** | **23** | **+11 tools** |
 86 | 
 87 | ## 🛠️ Technical Improvements
 88 | 
 89 | ### API Enhancements
 90 | - **Metadata API Support**: Full integration with Airtable's metadata API endpoints
 91 | - **Enhanced callAirtableAPI Function**: Already supported metadata endpoints
 92 | - **Improved Error Handling**: Better error messages for schema operations
 93 | 
 94 | ### Security & Safety
 95 | - **Confirmation Required**: Destructive operations require explicit confirmation
 96 | - **Validation Checks**: Proper field type and option validation
 97 | - **Safety Warnings**: Clear warnings for irreversible operations
 98 | 
 99 | ### Authentication
100 | - **Extended Scope Support**: Now leverages `schema.bases:read` and `schema.bases:write` scopes
101 | - **Backward Compatibility**: All existing functionality remains unchanged
102 | 
103 | ## 📚 New Capabilities
104 | 
105 | ### For Users
106 | - **Complete Base Discovery**: Find and explore all accessible bases
107 | - **Advanced Schema Inspection**: Understand table and field structures in detail
108 | - **Programmatic Table Creation**: Build tables through natural language
109 | - **Dynamic Field Management**: Add, modify, and remove fields as needed
110 | - **Comprehensive Field Reference**: Quick access to all available field types
111 | 
112 | ### For Developers
113 | - **Full CRUD for Schema**: Complete Create, Read, Update, Delete operations for tables and fields
114 | - **Metadata-First Approach**: Rich schema information before data operations
115 | - **Enhanced Automation**: Build complex Airtable structures programmatically
116 | 
117 | ## 🚀 Getting Started with v1.5.0
118 | 
119 | ### Installation
120 | ```bash
121 | npm install -g @rashidazarang/[email protected]
122 | ```
123 | 
124 | ### Required Token Scopes
125 | For full v1.5.0 functionality, ensure your Airtable Personal Access Token includes:
126 | - `data.records:read` - Read records
127 | - `data.records:write` - Create, update, delete records  
128 | - `schema.bases:read` - View table schemas (**New requirement**)
129 | - `schema.bases:write` - Create, modify tables and fields (**New requirement**)
130 | - `webhook:manage` - Webhook operations (optional)
131 | 
132 | ### Example Usage
133 | 
134 | ```javascript
135 | // Discover available bases
136 | "List all my accessible Airtable bases"
137 | 
138 | // Explore a base structure  
139 | "Show me the complete schema for this base"
140 | 
141 | // Create a new table
142 | "Create a new table called 'Projects' with fields: Name (text), Status (single select with options: Active, Completed, On Hold), and Due Date (date)"
143 | 
144 | // Add a field to existing table
145 | "Add a 'Priority' field to the Projects table as a single select with options: Low, Medium, High"
146 | 
147 | // Get detailed table information
148 | "Describe the Projects table with all field details"
149 | ```
150 | 
151 | ## 🔧 Breaking Changes
152 | 
153 | **None** - v1.5.0 is fully backward compatible with v1.4.0. All existing tools and functionality remain unchanged.
154 | 
155 | ## 🐛 Bug Fixes
156 | 
157 | - **Security**: Fixed clear-text logging of sensitive information (GitHub security alerts)
158 | - **API Error Handling**: Improved error messages for invalid table/field references
159 | - **Response Formatting**: Consistent JSON response structure across all tools
160 | 
161 | ## 🌟 What's Next
162 | 
163 | - Enhanced search capabilities with field-specific filtering
164 | - Batch operations for bulk table/field management
165 | - Advanced view creation and management
166 | - Performance optimizations for large bases
167 | 
168 | ## 📈 Performance & Compatibility
169 | 
170 | - **Node.js**: Requires Node.js 14+
171 | - **Rate Limits**: Respects Airtable's 5 requests/second limit
172 | - **Memory Usage**: Optimized for efficient schema operations
173 | - **Response Times**: Fast metadata operations with caching
174 | 
175 | ## 🤝 Community & Support
176 | 
177 | This release incorporates community feedback and feature requests. The v1.5.0 implementation draws inspiration from domdomegg's airtable-mcp-server while maintaining our unique webhook capabilities and enhanced error handling.
178 | 
179 | **GitHub**: https://github.com/rashidazarang/airtable-mcp  
180 | **NPM**: https://www.npmjs.com/package/@rashidazarang/airtable-mcp  
181 | **Issues**: https://github.com/rashidazarang/airtable-mcp/issues  
182 | 
183 | ---
184 | 
185 | 🎉 **Thank you for using Airtable MCP Server!** This release makes it the most comprehensive Airtable integration available for AI assistants, combining powerful schema management with robust webhook support.
```

--------------------------------------------------------------------------------
/tests/test_v1.6.0_comprehensive.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/bin/bash
  2 | 
  3 | # COMPREHENSIVE TEST SUITE - Airtable MCP Server v1.6.0
  4 | # Testing ALL 33 tools including 10 new v1.6.0 features
  5 | 
  6 | set -e
  7 | SERVER_URL="http://localhost:8010/mcp"
  8 | PASSED=0
  9 | FAILED=0
 10 | BATCH_RECORD_IDS=()
 11 | 
 12 | echo "🚀 COMPREHENSIVE TEST SUITE - v1.6.0"
 13 | echo "===================================="
 14 | echo "Testing ALL 33 tools with real API calls"
 15 | echo "New in v1.6.0: Batch operations, attachments, advanced views, base management"
 16 | echo ""
 17 | 
 18 | # Function to make MCP calls
 19 | call_tool() {
 20 |     local tool_name="$1"
 21 |     local params="$2"
 22 |     curl -s -X POST "$SERVER_URL" \
 23 |         -H "Content-Type: application/json" \
 24 |         -d "{\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": \"tools/call\", \"params\": {\"name\": \"$tool_name\", \"arguments\": $params}}"
 25 | }
 26 | 
 27 | # Enhanced test function
 28 | test_tool() {
 29 |     local tool_name="$1"
 30 |     local params="$2"
 31 |     local description="$3"
 32 |     local expect_fail="$4"
 33 |     
 34 |     echo -n "🔧 $tool_name: $description... "
 35 |     
 36 |     if result=$(call_tool "$tool_name" "$params" 2>&1); then
 37 |         if echo "$result" | jq -e '.result.content[0].text' > /dev/null 2>&1; then
 38 |             response_text=$(echo "$result" | jq -r '.result.content[0].text')
 39 |             if [[ "$expect_fail" == "true" ]]; then
 40 |                 if echo "$response_text" | grep -q "error\|Error\|not found\|Unknown field"; then
 41 |                     echo "✅ PASS (Expected failure)"
 42 |                     ((PASSED++))
 43 |                 else
 44 |                     echo "❌ FAIL (Should have failed)"
 45 |                     ((FAILED++))
 46 |                 fi
 47 |             else
 48 |                 echo "✅ PASS"
 49 |                 ((PASSED++))
 50 |                 # Store batch record IDs for cleanup
 51 |                 if [[ "$tool_name" == "batch_create_records" ]]; then
 52 |                     while IFS= read -r line; do
 53 |                         if [[ $line =~ ID:\ (rec[a-zA-Z0-9]+) ]]; then
 54 |                             BATCH_RECORD_IDS+=(${BASH_REMATCH[1]})
 55 |                         fi
 56 |                     done <<< "$response_text"
 57 |                 fi
 58 |             fi
 59 |         else
 60 |             if echo "$result" | jq -e '.error' > /dev/null 2>&1; then
 61 |                 error_msg=$(echo "$result" | jq -r '.error.message')
 62 |                 if [[ "$expect_fail" == "true" ]]; then
 63 |                     echo "✅ PASS (Expected error: $error_msg)"
 64 |                     ((PASSED++))
 65 |                 else
 66 |                     echo "❌ FAIL (API Error: $error_msg)"
 67 |                     ((FAILED++))
 68 |                 fi
 69 |             else
 70 |                 echo "❌ FAIL (Invalid response)"
 71 |                 ((FAILED++))
 72 |             fi
 73 |         fi
 74 |     else
 75 |         echo "❌ FAIL (Request failed)"
 76 |         ((FAILED++))
 77 |     fi
 78 | }
 79 | 
 80 | echo "📊 PHASE 1: Original Data Operations (7 tools)"
 81 | echo "=============================================="
 82 | 
 83 | test_tool "list_tables" "{}" "List all tables"
 84 | test_tool "list_records" "{\"table\": \"Test Table CRUD\", \"maxRecords\": 2}" "List limited records"
 85 | test_tool "search_records" "{\"table\": \"Test Table CRUD\", \"searchTerm\": \"test\"}" "Search records"
 86 | 
 87 | echo ""
 88 | echo "🪝 PHASE 2: Webhook Management (5 tools)"
 89 | echo "========================================"
 90 | 
 91 | test_tool "list_webhooks" "{}" "List existing webhooks"
 92 | 
 93 | echo ""
 94 | echo "🏗️ PHASE 3: Schema Management (11 tools)"
 95 | echo "========================================"
 96 | 
 97 | test_tool "list_bases" "{}" "List accessible bases"
 98 | test_tool "get_base_schema" "{}" "Get complete base schema"
 99 | test_tool "describe_table" "{\"table\": \"Test Table CRUD\"}" "Describe table details"
100 | test_tool "list_field_types" "{}" "List field types reference"
101 | test_tool "get_table_views" "{\"table\": \"Test Table CRUD\"}" "Get table views"
102 | 
103 | echo ""
104 | echo "🚀 PHASE 4: NEW v1.6.0 Batch Operations (4 tools)"
105 | echo "================================================="
106 | 
107 | test_tool "batch_create_records" "{\"table\": \"Test Table CRUD\", \"records\": [{\"fields\": {\"Name\": \"Batch Test A\", \"Description\": \"Batch created\", \"Status\": \"Testing\"}}, {\"fields\": {\"Name\": \"Batch Test B\", \"Description\": \"Also batch created\", \"Status\": \"Testing\"}}]}" "Create multiple records at once"
108 | 
109 | # Test batch operations with the created records
110 | if [ ${#BATCH_RECORD_IDS[@]} -ge 2 ]; then
111 |     test_tool "batch_update_records" "{\"table\": \"Test Table CRUD\", \"records\": [{\"id\": \"${BATCH_RECORD_IDS[0]}\", \"fields\": {\"Status\": \"Updated\"}}, {\"id\": \"${BATCH_RECORD_IDS[1]}\", \"fields\": {\"Status\": \"Updated\"}}]}" "Update multiple records at once"
112 |     test_tool "batch_delete_records" "{\"table\": \"Test Table CRUD\", \"recordIds\": [\"${BATCH_RECORD_IDS[0]}\", \"${BATCH_RECORD_IDS[1]}\"]}" "Delete multiple records at once"
113 | else
114 |     echo "⚠️  Skipping batch update/delete tests (no record IDs)"
115 |     ((FAILED += 2))
116 | fi
117 | 
118 | # Test batch limits
119 | test_tool "batch_create_records" "{\"table\": \"Test Table CRUD\", \"records\": []}" "Test with empty records array" "true"
120 | 
121 | echo ""
122 | echo "📎 PHASE 5: NEW v1.6.0 Attachment Operations (1 tool)"
123 | echo "===================================================="
124 | 
125 | # Test attachment with non-existent field (expected to fail)
126 | test_tool "upload_attachment" "{\"table\": \"Test Table CRUD\", \"recordId\": \"recDummyID\", \"fieldName\": \"NonExistentField\", \"url\": \"https://via.placeholder.com/150.png\"}" "Test attachment to non-existent field" "true"
127 | 
128 | echo ""
129 | echo "👁️ PHASE 6: NEW v1.6.0 Advanced Views (2 tools)"
130 | echo "==============================================="
131 | 
132 | # Test view operations (some may fail if permissions don't allow)
133 | test_tool "get_view_metadata" "{\"table\": \"Test Table CRUD\", \"viewId\": \"viw123InvalidID\"}" "Test view metadata with invalid ID" "true"
134 | 
135 | echo ""
136 | echo "🏢 PHASE 7: NEW v1.6.0 Base Management (3 tools)"
137 | echo "==============================================="
138 | 
139 | test_tool "list_collaborators" "{}" "List base collaborators"
140 | test_tool "list_shares" "{}" "List shared views"
141 | 
142 | # Test create_base (may fail without workspace permissions)
143 | test_tool "create_base" "{\"name\": \"Test Base\", \"tables\": [{\"name\": \"Test Table\", \"fields\": [{\"name\": \"Name\", \"type\": \"singleLineText\"}]}]}" "Test base creation (may fail due to permissions)" "true"
144 | 
145 | echo ""
146 | echo "⚠️  PHASE 8: Error Handling & Edge Cases"
147 | echo "======================================="
148 | 
149 | test_tool "batch_create_records" "{\"table\": \"NonExistentTable\", \"records\": [{\"fields\": {\"Name\": \"Test\"}}]}" "Test batch create with non-existent table" "true"
150 | test_tool "get_view_metadata" "{\"table\": \"NonExistentTable\", \"viewId\": \"viwTest\"}" "Test view metadata with non-existent table" "true"
151 | 
152 | echo ""
153 | echo "📈 FINAL TEST RESULTS - v1.6.0"
154 | echo "==============================="
155 | echo "✅ Passed: $PASSED"
156 | echo "❌ Failed: $FAILED"
157 | echo "📊 Total Tests: $((PASSED + FAILED))"
158 | echo "📊 Success Rate: $(echo "scale=1; $PASSED * 100 / ($PASSED + $FAILED)" | bc -l)%"
159 | 
160 | if [ $FAILED -eq 0 ]; then
161 |     echo ""
162 |     echo "🎉 🎉 🎉 ALL TESTS PASSED! 🎉 🎉 🎉"
163 |     echo ""
164 |     echo "✅ v1.6.0 is READY FOR PRODUCTION!"
165 |     echo ""
166 |     echo "🚀 NEW v1.6.0 ACHIEVEMENTS:"
167 |     echo "• 33 total tools (+ 10 from v1.5.0)"
168 |     echo "• Batch operations (create/update/delete up to 10 records)"
169 |     echo "• Attachment management via URLs"
170 |     echo "• Advanced view metadata and creation"
171 |     echo "• Base management and collaboration tools"
172 |     echo "• Enhanced error handling and validation"
173 |     echo ""
174 |     echo "📦 Ready for GitHub and NPM release!"
175 |     exit 0
176 | else
177 |     echo ""
178 |     echo "❌ SOME TESTS FAILED"
179 |     echo "Review failures above. Some failures may be expected (permissions, non-existent resources)."
180 |     echo ""
181 |     echo "🎯 v1.6.0 SUMMARY:"
182 |     echo "• Core functionality working"
183 |     echo "• New batch operations implemented"
184 |     echo "• Attachment support added"
185 |     echo "• Advanced features may need specific permissions"
186 |     exit 1
187 | fi
```

--------------------------------------------------------------------------------
/src/typescript/ai-prompts.d.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * AI-Powered Prompt Templates Type Definitions
  3 |  * Enterprise-grade TypeScript types for all 10 AI prompt templates
  4 |  */
  5 | 
  6 | import { PromptSchema, PromptArgument } from './index';
  7 | 
  8 | // ============================================================================
  9 | // AI Prompt Template Interfaces
 10 | // ============================================================================
 11 | 
 12 | export interface AnalyzeDataPrompt {
 13 |   table: string;
 14 |   analysis_type?: 'trends' | 'statistical' | 'patterns' | 'predictive' | 'anomaly_detection' | 'correlation_matrix';
 15 |   field_focus?: string;
 16 |   time_dimension?: string;
 17 |   confidence_level?: 0.90 | 0.95 | 0.99;
 18 | }
 19 | 
 20 | export interface CreateReportPrompt {
 21 |   table: string;
 22 |   report_type: 'executive_summary' | 'detailed_analysis' | 'dashboard' | 'stakeholder_report';
 23 |   target_audience: 'executives' | 'managers' | 'analysts' | 'technical_team';
 24 |   include_recommendations?: boolean;
 25 |   time_period?: string;
 26 |   format_preference?: 'narrative' | 'bullet_points' | 'charts' | 'mixed';
 27 | }
 28 | 
 29 | export interface DataInsightsPrompt {
 30 |   table: string;
 31 |   insight_type: 'business_intelligence' | 'trend_analysis' | 'performance_metrics' | 'opportunity_identification';
 32 |   focus_areas?: string[];
 33 |   comparison_period?: string;
 34 |   include_forecasting?: boolean;
 35 |   stakeholder_context?: string;
 36 | }
 37 | 
 38 | export interface OptimizeWorkflowPrompt {
 39 |   table: string;
 40 |   current_process_description: string;
 41 |   optimization_goals: ('efficiency' | 'accuracy' | 'speed' | 'cost_reduction' | 'compliance')[];
 42 |   constraints?: string[];
 43 |   automation_preference?: 'minimal' | 'moderate' | 'aggressive';
 44 |   change_tolerance?: 'low' | 'medium' | 'high';
 45 | }
 46 | 
 47 | export interface SmartSchemaDesignPrompt {
 48 |   purpose: string;
 49 |   data_types: string[];
 50 |   expected_volume: 'small' | 'medium' | 'large' | 'enterprise';
 51 |   compliance_requirements?: ('GDPR' | 'HIPAA' | 'SOX' | 'PCI_DSS')[];
 52 |   performance_priorities?: ('query_speed' | 'storage_efficiency' | 'scalability' | 'maintainability')[];
 53 |   integration_needs?: string[];
 54 |   user_access_patterns?: string;
 55 | }
 56 | 
 57 | export interface DataQualityAuditPrompt {
 58 |   table: string;
 59 |   quality_dimensions: ('completeness' | 'accuracy' | 'consistency' | 'timeliness' | 'validity' | 'uniqueness')[];
 60 |   automated_fixes?: boolean;
 61 |   severity_threshold?: 'low' | 'medium' | 'high' | 'critical';
 62 |   compliance_context?: string;
 63 |   reporting_requirements?: string[];
 64 | }
 65 | 
 66 | export interface PredictiveAnalyticsPrompt {
 67 |   table: string;
 68 |   target_field: string;
 69 |   prediction_periods?: number;
 70 |   algorithm?: 'linear_regression' | 'arima' | 'exponential_smoothing' | 'random_forest' | 'neural_network';
 71 |   include_confidence_intervals?: boolean;
 72 |   historical_periods?: number;
 73 |   external_factors?: string[];
 74 |   business_context?: string;
 75 | }
 76 | 
 77 | export interface NaturalLanguageQueryPrompt {
 78 |   question: string;
 79 |   tables?: string[];
 80 |   response_format?: 'natural_language' | 'structured_data' | 'visualization_ready' | 'action_items';
 81 |   context_awareness?: boolean;
 82 |   confidence_threshold?: number;
 83 |   clarifying_questions?: boolean;
 84 | }
 85 | 
 86 | export interface SmartDataTransformationPrompt {
 87 |   source_table: string;
 88 |   target_schema?: string;
 89 |   transformation_goals: ('normalization' | 'aggregation' | 'enrichment' | 'validation' | 'standardization')[];
 90 |   data_quality_rules?: string[];
 91 |   preserve_history?: boolean;
 92 |   validation_strategy?: 'strict' | 'permissive' | 'custom';
 93 |   error_handling?: 'fail_fast' | 'log_and_continue' | 'manual_review';
 94 | }
 95 | 
 96 | export interface AutomationRecommendationsPrompt {
 97 |   workflow_description: string;
 98 |   current_pain_points: string[];
 99 |   automation_scope: 'single_task' | 'workflow_segment' | 'end_to_end' | 'cross_system';
100 |   technical_constraints?: string[];
101 |   business_impact_priority?: ('cost_savings' | 'time_efficiency' | 'error_reduction' | 'scalability')[];
102 |   implementation_timeline?: 'immediate' | 'short_term' | 'medium_term' | 'long_term';
103 |   risk_tolerance?: 'conservative' | 'moderate' | 'aggressive';
104 | }
105 | 
106 | // ============================================================================
107 | // AI Prompt Response Types
108 | // ============================================================================
109 | 
110 | export interface AnalysisResult {
111 |   summary: string;
112 |   key_findings: string[];
113 |   statistical_measures?: {
114 |     mean?: number;
115 |     median?: number;
116 |     std_deviation?: number;
117 |     correlation_coefficients?: Record<string, number>;
118 |     confidence_intervals?: Array<{ field: string; lower: number; upper: number; confidence: number }>;
119 |   };
120 |   trends?: Array<{
121 |     field: string;
122 |     direction: 'increasing' | 'decreasing' | 'stable' | 'volatile';
123 |     strength: 'weak' | 'moderate' | 'strong';
124 |     significance: number;
125 |   }>;
126 |   anomalies?: Array<{
127 |     record_id: string;
128 |     field: string;
129 |     expected_value: unknown;
130 |     actual_value: unknown;
131 |     deviation_score: number;
132 |   }>;
133 |   recommendations: string[];
134 |   next_steps: string[];
135 | }
136 | 
137 | export interface ReportResult {
138 |   title: string;
139 |   executive_summary: string;
140 |   detailed_sections: Array<{
141 |     heading: string;
142 |     content: string;
143 |     supporting_data?: unknown[];
144 |     visualizations?: Array<{ type: string; data: unknown; description: string }>;
145 |   }>;
146 |   key_metrics: Record<string, { value: unknown; change: string; significance: string }>;
147 |   recommendations: Array<{
148 |     priority: 'high' | 'medium' | 'low';
149 |     recommendation: string;
150 |     expected_impact: string;
151 |     implementation_effort: 'low' | 'medium' | 'high';
152 |   }>;
153 |   appendices?: Array<{ title: string; content: string }>;
154 | }
155 | 
156 | export interface WorkflowOptimizationResult {
157 |   current_state_analysis: {
158 |     efficiency_score: number;
159 |     bottlenecks: Array<{ step: string; impact: 'high' | 'medium' | 'low'; description: string }>;
160 |     resource_utilization: Record<string, number>;
161 |   };
162 |   optimization_recommendations: Array<{
163 |     category: 'automation' | 'process_redesign' | 'tool_integration' | 'skill_development';
164 |     recommendation: string;
165 |     expected_benefits: string[];
166 |     implementation_complexity: 'simple' | 'moderate' | 'complex';
167 |     estimated_roi: string;
168 |     timeline: string;
169 |   }>;
170 |   implementation_roadmap: Array<{
171 |     phase: number;
172 |     duration: string;
173 |     objectives: string[];
174 |     deliverables: string[];
175 |     success_metrics: string[];
176 |   }>;
177 |   risk_assessment: Array<{
178 |     risk: string;
179 |     probability: 'low' | 'medium' | 'high';
180 |     impact: 'low' | 'medium' | 'high';
181 |     mitigation: string;
182 |   }>;
183 | }
184 | 
185 | export interface SchemaDesignResult {
186 |   recommended_schema: {
187 |     tables: Array<{
188 |       name: string;
189 |       purpose: string;
190 |       fields: Array<{
191 |         name: string;
192 |         type: string;
193 |         constraints: string[];
194 |         description: string;
195 |       }>;
196 |       relationships: Array<{
197 |         type: 'one_to_one' | 'one_to_many' | 'many_to_many';
198 |         target_table: string;
199 |         description: string;
200 |       }>;
201 |     }>;
202 |   };
203 |   design_principles: string[];
204 |   performance_considerations: string[];
205 |   scalability_notes: string[];
206 |   compliance_alignment: Record<string, string[]>;
207 |   migration_strategy?: {
208 |     phases: Array<{ phase: number; description: string; estimated_time: string }>;
209 |     data_migration_notes: string[];
210 |     validation_checkpoints: string[];
211 |   };
212 | }
213 | 
214 | export interface PredictionResult {
215 |   predictions: Array<{
216 |     period: string;
217 |     predicted_value: number;
218 |     confidence_interval?: { lower: number; upper: number };
219 |     probability_bands?: Array<{ probability: number; range: [number, number] }>;
220 |   }>;
221 |   model_performance: {
222 |     algorithm_used: string;
223 |     accuracy_metrics: Record<string, number>;
224 |     feature_importance?: Record<string, number>;
225 |     validation_results: Record<string, number>;
226 |   };
227 |   business_insights: {
228 |     trend_direction: 'positive' | 'negative' | 'stable';
229 |     seasonality_detected: boolean;
230 |     external_factors_impact: string[];
231 |     risk_factors: string[];
232 |   };
233 |   recommendations: Array<{
234 |     type: 'operational' | 'strategic' | 'tactical';
235 |     recommendation: string;
236 |     timing: string;
237 |     confidence: number;
238 |   }>;
239 | }
240 | 
241 | // ============================================================================
242 | // Prompt Template Definitions (Type-Safe)
243 | // ============================================================================
244 | 
245 | // AI prompt templates are defined in prompt-templates.ts for runtime use
246 | 
247 | // ============================================================================
248 | // Export All AI Prompt Types
249 | // ============================================================================
250 | 
251 | export {
252 |   AnalyzeDataPrompt,
253 |   CreateReportPrompt,
254 |   DataInsightsPrompt,
255 |   OptimizeWorkflowPrompt,
256 |   SmartSchemaDesignPrompt,
257 |   DataQualityAuditPrompt,
258 |   PredictiveAnalyticsPrompt,
259 |   NaturalLanguageQueryPrompt,
260 |   SmartDataTransformationPrompt,
261 |   AutomationRecommendationsPrompt,
262 |   
263 |   AnalysisResult,
264 |   ReportResult,
265 |   WorkflowOptimizationResult,
266 |   SchemaDesignResult,
267 |   PredictionResult
268 | };
```

--------------------------------------------------------------------------------
/src/typescript/app/types.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | 
  3 | /**
  4 |  * Shared Zod schemas and TypeScript types for Airtable Brain tools.
  5 |  * Keep these aligned with the JSON Schemas under docs/prd/schemas.
  6 |  */
  7 | 
  8 | const describeInputBase = z
  9 |   .object({
 10 |     scope: z.enum(['base', 'table']),
 11 |     baseId: z.string().min(1, 'baseId is required'),
 12 |     table: z
 13 |       .string()
 14 |       .min(1, 'table is required when scope=table')
 15 |       .optional(),
 16 |     includeFields: z.boolean().optional().default(true),
 17 |     includeViews: z.boolean().optional().default(false)
 18 |   })
 19 |   .strict();
 20 | 
 21 | export const describeInputSchema = describeInputBase.superRefine((data, ctx) => {
 22 |     if (data.scope === 'table' && !data.table) {
 23 |       ctx.addIssue({
 24 |         code: z.ZodIssueCode.custom,
 25 |         path: ['table'],
 26 |         message: 'table is required when scope is "table"'
 27 |       });
 28 |     }
 29 |   });
 30 | 
 31 | export const describeInputShape = describeInputBase.shape;
 32 | 
 33 | const describeFieldSchema = z
 34 |   .object({
 35 |     id: z.string(),
 36 |     name: z.string(),
 37 |     type: z.string(),
 38 |     options: z.record(z.unknown()).optional()
 39 |   })
 40 |   .passthrough();
 41 | 
 42 | const describeTableSchema = z
 43 |   .object({
 44 |     id: z.string(),
 45 |     name: z.string(),
 46 |     description: z.string().optional(),
 47 |     primaryFieldId: z.string().optional(),
 48 |     fields: z.array(describeFieldSchema).optional(),
 49 |     views: z.array(z.record(z.unknown())).optional()
 50 |   })
 51 |   .passthrough();
 52 | 
 53 | export const describeOutputSchema = z
 54 |   .object({
 55 |     base: z
 56 |       .object({
 57 |         id: z.string(),
 58 |         name: z.string()
 59 |       })
 60 |       .passthrough(),
 61 |     tables: z.array(describeTableSchema).optional(),
 62 |     views: z.array(z.record(z.unknown())).optional()
 63 |   })
 64 |   .strict();
 65 | 
 66 | const sortDirectionSchema = z.enum(['asc', 'desc']);
 67 | 
 68 | const queryInputBase = z
 69 |   .object({
 70 |     baseId: z.string().min(1, 'baseId is required'),
 71 |     table: z.string().min(1, 'table is required'),
 72 |     fields: z.array(z.string().min(1)).optional(),
 73 |     filterByFormula: z.string().optional(),
 74 |     view: z.string().optional(),
 75 |     sorts: z
 76 |       .array(
 77 |         z
 78 |           .object({
 79 |             field: z.string().min(1),
 80 |             direction: sortDirectionSchema.optional().default('asc')
 81 |           })
 82 |           .strict()
 83 |       )
 84 |       .optional(),
 85 |     pageSize: z
 86 |       .number()
 87 |       .int()
 88 |       .min(1)
 89 |       .max(100)
 90 |       .optional(),
 91 |     maxRecords: z
 92 |       .number()
 93 |       .int()
 94 |       .min(1)
 95 |       .optional(),
 96 |     offset: z.string().optional(),
 97 |     returnFieldsByFieldId: z.boolean().optional().default(false)
 98 |   })
 99 |   .strict();
100 | 
101 | export const queryInputSchema = queryInputBase;
102 | export const queryInputShape = queryInputBase.shape;
103 | 
104 | const recordSchema = z
105 |   .object({
106 |     id: z.string(),
107 |     createdTime: z.string().optional(),
108 |     fields: z.record(z.unknown())
109 |   })
110 |   .strict();
111 | 
112 | export const queryOutputSchema = z
113 |   .object({
114 |     records: z.array(recordSchema),
115 |     offset: z.string().optional(),
116 |     summary: z
117 |       .object({
118 |         returned: z.number().int().nonnegative(),
119 |         hasMore: z.boolean()
120 |       })
121 |       .strict()
122 |       .optional()
123 |   })
124 |   .strict();
125 | 
126 | export const createInputSchema = z
127 |   .object({
128 |     baseId: z.string().min(1),
129 |     table: z.string().min(1),
130 |     records: z
131 |       .array(
132 |         z
133 |           .object({
134 |             fields: z.record(z.unknown())
135 |           })
136 |           .strict()
137 |       )
138 |       .min(1),
139 |     typecast: z.boolean().optional().default(false),
140 |     idempotencyKey: z.string().min(1).optional(),
141 |     dryRun: z.boolean().optional().default(false)
142 |   })
143 |   .strict();
144 | 
145 | const createDiffSchema = z
146 |   .object({
147 |     added: z.number().int().nonnegative(),
148 |     updated: z.number().int().nonnegative(),
149 |     unchanged: z.number().int().nonnegative()
150 |   })
151 |   .strict();
152 | 
153 | export const createOutputSchema = z
154 |   .object({
155 |     diff: createDiffSchema,
156 |     records: z.array(recordSchema).optional(),
157 |     dryRun: z.boolean(),
158 |     warnings: z.array(z.string()).optional()
159 |   })
160 |   .strict();
161 | 
162 | const conflictSchema = z
163 |   .object({
164 |     id: z.string(),
165 |     field: z.string(),
166 |     before: z.unknown().optional(),
167 |     after: z.unknown().optional(),
168 |     current: z.unknown()
169 |   })
170 |   .strict();
171 | 
172 | const mutationDiffSchema = z
173 |   .object({
174 |     added: z.number().int().nonnegative(),
175 |     updated: z.number().int().nonnegative(),
176 |     unchanged: z.number().int().nonnegative(),
177 |     conflicts: z.number().int().nonnegative()
178 |   })
179 |   .strict();
180 | 
181 | export const updateOutputSchema = z
182 |   .object({
183 |     diff: mutationDiffSchema,
184 |     records: z.array(recordSchema).optional(),
185 |     dryRun: z.boolean(),
186 |     conflicts: z.array(conflictSchema).optional()
187 |   })
188 |   .strict();
189 | 
190 | export const updateInputSchema = z
191 |   .object({
192 |     baseId: z.string().min(1),
193 |     table: z.string().min(1),
194 |     records: z
195 |       .array(
196 |         z
197 |           .object({
198 |             id: z.string(),
199 |             fields: z.record(z.unknown())
200 |           })
201 |           .strict()
202 |       )
203 |       .min(1),
204 |     typecast: z.boolean().optional().default(false),
205 |     idempotencyKey: z.string().min(1).optional(),
206 |     dryRun: z.boolean().optional().default(false),
207 |     conflictStrategy: z
208 |       .enum(['fail_on_conflict', 'server_merge', 'client_merge'])
209 |       .optional()
210 |       .default('fail_on_conflict'),
211 |     ifUnchangedHash: z.string().optional()
212 |   })
213 |   .strict();
214 | 
215 | export const upsertInputSchema = z
216 |   .object({
217 |     baseId: z.string().min(1),
218 |     table: z.string().min(1),
219 |     records: z
220 |       .array(
221 |         z
222 |           .object({
223 |             fields: z.record(z.unknown())
224 |           })
225 |           .strict()
226 |       )
227 |       .min(1),
228 |     performUpsert: z
229 |       .object({
230 |         fieldsToMergeOn: z.array(z.string().min(1)).min(1)
231 |       })
232 |       .strict(),
233 |     typecast: z.boolean().optional().default(false),
234 |     idempotencyKey: z.string().min(1).optional(),
235 |     dryRun: z.boolean().optional().default(false),
236 |     conflictStrategy: z
237 |       .enum(['fail_on_conflict', 'server_merge', 'client_merge'])
238 |       .optional()
239 |       .default('fail_on_conflict')
240 |   })
241 |   .strict();
242 | 
243 | export const upsertOutputSchema = z
244 |   .object({
245 |     diff: mutationDiffSchema,
246 |     records: z.array(recordSchema).optional(),
247 |     dryRun: z.boolean(),
248 |     conflicts: z.array(conflictSchema).optional()
249 |   })
250 |   .strict();
251 | 
252 | export const listExceptionsInputSchema = z
253 |   .object({
254 |     since: z.string().optional(),
255 |     severity: z.enum(['info', 'warning', 'error']).optional(),
256 |     limit: z.number().int().min(1).max(500).optional().default(100),
257 |     cursor: z.string().optional()
258 |   })
259 |   .strict();
260 | 
261 | export const exceptionItemSchema = z
262 |   .object({
263 |     id: z.string(),
264 |     timestamp: z.string(),
265 |     severity: z.enum(['info', 'warning', 'error']),
266 |     category: z.enum(['rate_limit', 'validation', 'auth', 'conflict', 'schema_drift', 'other']),
267 |     summary: z.string(),
268 |     details: z.string().optional(),
269 |     proposedFix: z.record(z.unknown()).optional()
270 |   })
271 |   .strict();
272 | 
273 | export const listExceptionsOutputSchema = z
274 |   .object({
275 |     items: z.array(exceptionItemSchema),
276 |     cursor: z.string().optional()
277 |   })
278 |   .strict();
279 | 
280 | const allowedOperations = ['describe', 'query', 'create', 'update', 'upsert'] as const;
281 | 
282 | export const governanceOutputSchema = z
283 |   .object({
284 |     allowedBases: z.array(z.string()),
285 |     allowedTables: z
286 |       .array(
287 |         z
288 |           .object({
289 |             baseId: z.string(),
290 |             table: z.string()
291 |           })
292 |           .strict()
293 |       )
294 |       .optional()
295 |       .default([]),
296 |     allowedOperations: z
297 |       .array(z.enum(allowedOperations))
298 |       .default([...allowedOperations]),
299 |     piiFields: z
300 |       .array(
301 |         z
302 |           .object({
303 |             baseId: z.string(),
304 |             table: z.string(),
305 |             field: z.string(),
306 |             policy: z.enum(['mask', 'hash', 'drop'])
307 |           })
308 |           .strict()
309 |       )
310 |       .optional()
311 |       .default([]),
312 |     redactionPolicy: z.enum(['mask_all_pii', 'mask_on_inline', 'none']).default('mask_on_inline'),
313 |     loggingPolicy: z.enum(['errors_only', 'minimal', 'verbose']).default('minimal'),
314 |     retentionDays: z.number().int().min(0).default(7)
315 |   })
316 |   .strict();
317 | 
318 | export type DescribeInput = z.infer<typeof describeInputSchema>;
319 | export type DescribeOutput = z.infer<typeof describeOutputSchema>;
320 | 
321 | export type QueryInput = z.infer<typeof queryInputSchema>;
322 | export type QueryOutput = z.infer<typeof queryOutputSchema>;
323 | 
324 | export type CreateInput = z.infer<typeof createInputSchema>;
325 | export type CreateOutput = z.infer<typeof createOutputSchema>;
326 | export type UpdateInput = z.infer<typeof updateInputSchema>;
327 | export type UpdateOutput = z.infer<typeof updateOutputSchema>;
328 | export type UpsertInput = z.infer<typeof upsertInputSchema>;
329 | export type UpsertOutput = z.infer<typeof upsertOutputSchema>;
330 | 
331 | export type ListExceptionsInput = z.infer<typeof listExceptionsInputSchema>;
332 | export type ExceptionItem = z.infer<typeof exceptionItemSchema>;
333 | export type ListExceptionsOutput = z.infer<typeof listExceptionsOutputSchema>;
334 | 
335 | export type GovernanceSnapshot = z.infer<typeof governanceOutputSchema>;
336 | 
```

--------------------------------------------------------------------------------
/src/typescript/index.d.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Airtable MCP Server TypeScript Definitions
  3 |  * Enterprise-grade type safety for AI-powered Airtable operations
  4 |  */
  5 | 
  6 | // ============================================================================
  7 | // MCP Protocol Types (2024-11-05 Specification)
  8 | // ============================================================================
  9 | 
 10 | export interface MCPRequest {
 11 |   jsonrpc: '2.0';
 12 |   id: string | number;
 13 |   method: string;
 14 |   params?: Record<string, unknown>;
 15 | }
 16 | 
 17 | export interface MCPResponse {
 18 |   jsonrpc: '2.0';
 19 |   id: string | number;
 20 |   result?: unknown;
 21 |   error?: MCPError;
 22 | }
 23 | 
 24 | export interface MCPError {
 25 |   code: number;
 26 |   message: string;
 27 |   data?: unknown;
 28 | }
 29 | 
 30 | export interface MCPServerCapabilities {
 31 |   tools?: {
 32 |     listChanged?: boolean;
 33 |   };
 34 |   prompts?: {
 35 |     listChanged?: boolean;
 36 |   };
 37 |   resources?: {
 38 |     subscribe?: boolean;
 39 |     listChanged?: boolean;
 40 |   };
 41 |   roots?: {
 42 |     listChanged?: boolean;
 43 |   };
 44 |   sampling?: Record<string, unknown>;
 45 |   logging?: Record<string, unknown>;
 46 | }
 47 | 
 48 | export interface MCPServerInfo {
 49 |   name: string;
 50 |   version: string;
 51 |   protocolVersion: string;
 52 |   capabilities: MCPServerCapabilities;
 53 | }
 54 | 
 55 | // ============================================================================
 56 | // Tool Schema Types
 57 | // ============================================================================
 58 | 
 59 | export interface ToolParameter {
 60 |   type: 'string' | 'number' | 'boolean' | 'object' | 'array';
 61 |   description: string;
 62 |   required?: boolean;
 63 |   default?: unknown;
 64 |   enum?: string[];
 65 | }
 66 | 
 67 | export interface ToolSchema {
 68 |   name: string;
 69 |   description: string;
 70 |   inputSchema: {
 71 |     type: 'object';
 72 |     properties: Record<string, ToolParameter>;
 73 |     required?: string[];
 74 |   };
 75 | }
 76 | 
 77 | // ============================================================================
 78 | // AI Prompt Types
 79 | // ============================================================================
 80 | 
 81 | export interface PromptArgument {
 82 |   name: string;
 83 |   description: string;
 84 |   required: boolean;
 85 |   type?: 'string' | 'number' | 'boolean';
 86 |   enum?: string[];
 87 | }
 88 | 
 89 | export interface PromptSchema {
 90 |   name: string;
 91 |   description: string;
 92 |   arguments: PromptArgument[];
 93 | }
 94 | 
 95 | export type AnalysisType = 
 96 |   | 'trends' 
 97 |   | 'statistical' 
 98 |   | 'patterns' 
 99 |   | 'predictive' 
100 |   | 'anomaly_detection' 
101 |   | 'correlation_matrix';
102 | 
103 | export type ConfidenceLevel = 0.90 | 0.95 | 0.99;
104 | 
105 | export interface AnalysisOptions {
106 |   table: string;
107 |   analysis_type?: AnalysisType;
108 |   field_focus?: string;
109 |   time_dimension?: string;
110 |   confidence_level?: ConfidenceLevel;
111 | }
112 | 
113 | export interface PredictiveAnalyticsOptions {
114 |   table: string;
115 |   target_field: string;
116 |   prediction_periods?: number;
117 |   algorithm?: 'linear_regression' | 'arima' | 'exponential_smoothing' | 'random_forest';
118 |   include_confidence_intervals?: boolean;
119 |   historical_periods?: number;
120 | }
121 | 
122 | export interface StatisticalResult {
123 |   confidence_interval: [number, number];
124 |   significance_level: number;
125 |   p_value?: number;
126 |   correlation_coefficient?: number;
127 | }
128 | 
129 | // ============================================================================
130 | // Airtable API Types
131 | // ============================================================================
132 | 
133 | export interface AirtableFieldType {
134 |   type: 'singleLineText' | 'multilineText' | 'richText' | 'email' | 'url' | 'phoneNumber' |
135 |         'number' | 'percent' | 'currency' | 'singleSelect' | 'multipleSelects' |
136 |         'date' | 'dateTime' | 'checkbox' | 'rating' | 'formula' | 'rollup' |
137 |         'count' | 'lookup' | 'createdTime' | 'lastModifiedTime' | 'createdBy' |
138 |         'lastModifiedBy' | 'attachment' | 'barcode' | 'button';
139 | }
140 | 
141 | export interface AirtableField {
142 |   id: string;
143 |   name: string;
144 |   type: AirtableFieldType['type'];
145 |   options?: Record<string, unknown>;
146 |   description?: string;
147 | }
148 | 
149 | export interface AirtableTable {
150 |   id: string;
151 |   name: string;
152 |   description?: string;
153 |   primaryFieldId: string;
154 |   fields: AirtableField[];
155 |   views: AirtableView[];
156 | }
157 | 
158 | export interface AirtableView {
159 |   id: string;
160 |   name: string;
161 |   type: 'grid' | 'form' | 'calendar' | 'gallery' | 'kanban';
162 | }
163 | 
164 | export interface AirtableRecord {
165 |   id: string;
166 |   fields: Record<string, unknown>;
167 |   createdTime: string;
168 | }
169 | 
170 | export interface AirtableBase {
171 |   id: string;
172 |   name: string;
173 |   permissionLevel: 'read' | 'comment' | 'edit' | 'create';
174 |   tables: AirtableTable[];
175 | }
176 | 
177 | export interface AirtableWebhook {
178 |   id: string;
179 |   macSecretBase64: string;
180 |   expirationTime: string;
181 |   notificationUrl: string;
182 |   isHookEnabled: boolean;
183 |   cursorForNextPayload: number;
184 |   lastSuccessfulNotificationTime?: string;
185 | }
186 | 
187 | export interface WebhookPayload {
188 |   timestamp: string;
189 |   base: {
190 |     id: string;
191 |   };
192 |   webhook: {
193 |     id: string;
194 |   };
195 |   changedTablesById: Record<string, {
196 |     changedRecordsById: Record<string, {
197 |       current?: AirtableRecord;
198 |       previous?: AirtableRecord;
199 |     }>;
200 |   }>;
201 | }
202 | 
203 | // ============================================================================
204 | // Server Configuration Types
205 | // ============================================================================
206 | 
207 | export interface ServerConfig {
208 |   PORT: number;
209 |   HOST: string;
210 |   MAX_REQUESTS_PER_MINUTE: number;
211 |   LOG_LEVEL: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
212 | }
213 | 
214 | export interface AuthConfig {
215 |   AIRTABLE_TOKEN: string;
216 |   AIRTABLE_BASE_ID: string;
217 | }
218 | 
219 | export interface OAuth2Config {
220 |   client_id: string;
221 |   redirect_uri: string;
222 |   state: string;
223 |   code_challenge?: string;
224 |   code_challenge_method?: 'S256';
225 | }
226 | 
227 | // ============================================================================
228 | // Batch Operation Types
229 | // ============================================================================
230 | 
231 | export interface BatchCreateRecord {
232 |   fields: Record<string, unknown>;
233 | }
234 | 
235 | export interface BatchUpdateRecord {
236 |   id: string;
237 |   fields: Record<string, unknown>;
238 | }
239 | 
240 | export interface BatchDeleteRecord {
241 |   id: string;
242 | }
243 | 
244 | export interface BatchUpsertRecord {
245 |   key_field: string;
246 |   key_value: string;
247 |   fields: Record<string, unknown>;
248 | }
249 | 
250 | // ============================================================================
251 | // Advanced Analytics Types
252 | // ============================================================================
253 | 
254 | export interface DataQualityReport {
255 |   total_records: number;
256 |   missing_values: Record<string, number>;
257 |   duplicate_records: string[];
258 |   data_types: Record<string, string>;
259 |   quality_score: number;
260 |   recommendations: string[];
261 | }
262 | 
263 | export interface WorkflowOptimization {
264 |   current_efficiency: number;
265 |   bottlenecks: string[];
266 |   automation_opportunities: Array<{
267 |     field: string;
268 |     suggestion: string;
269 |     impact_level: 'high' | 'medium' | 'low';
270 |     implementation_complexity: 'simple' | 'moderate' | 'complex';
271 |   }>;
272 |   estimated_time_savings: string;
273 | }
274 | 
275 | export interface SchemaOptimization {
276 |   field_recommendations: Array<{
277 |     field: string;
278 |     current_type: string;
279 |     suggested_type: string;
280 |     reason: string;
281 |   }>;
282 |   index_suggestions: string[];
283 |   normalization_opportunities: string[];
284 |   compliance_notes: string[];
285 | }
286 | 
287 | // ============================================================================
288 | // Root Directory Types
289 | // ============================================================================
290 | 
291 | export interface RootDirectory {
292 |   uri: string;
293 |   name: string;
294 |   description?: string;
295 | }
296 | 
297 | // ============================================================================
298 | // Error Types (defined in errors.ts)
299 | // ============================================================================
300 | 
301 | export interface AirtableError extends Error {
302 |   code: string;
303 |   statusCode?: number;
304 | }
305 | 
306 | export interface ValidationError extends Error {
307 |   field: string;
308 | }
309 | 
310 | // ============================================================================
311 | // Utility Types
312 | // ============================================================================
313 | 
314 | export type DeepPartial<T> = {
315 |   [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
316 | };
317 | 
318 | export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
319 | 
320 | export type OptionalFields<T, K extends keyof T> = T & Partial<Pick<T, K>>;
321 | 
322 | // ============================================================================
323 | // Main Server Class Type
324 | // ============================================================================
325 | 
326 | export interface AirtableMCPServer {
327 |   config: ServerConfig;
328 |   authConfig: AuthConfig;
329 |   tools: ToolSchema[];
330 |   prompts: PromptSchema[];
331 |   
332 |   initialize(capabilities: MCPServerCapabilities): Promise<MCPServerInfo>;
333 |   handleToolCall(name: string, params: Record<string, unknown>): Promise<unknown>;
334 |   handlePromptGet(name: string, args: Record<string, unknown>): Promise<{ messages: Array<{ role: string; content: { type: string; text: string } }> }>;
335 |   start(): Promise<void>;
336 |   stop(): Promise<void>;
337 | }
338 | 
339 | // ============================================================================
340 | // Export All Types
341 | // ============================================================================
342 | 
343 | export * from './tools';
344 | export * from './ai-prompts';
```

--------------------------------------------------------------------------------
/src/typescript/tools.d.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /**
  2 |  * Tool Schema Type Definitions
  3 |  * Comprehensive TypeScript types for all 33 Airtable MCP tools
  4 |  */
  5 | 
  6 | import { ToolSchema } from './index';
  7 | 
  8 | // ============================================================================
  9 | // Data Operation Tool Interfaces
 10 | // ============================================================================
 11 | 
 12 | export interface ListTablesInput {
 13 |   include_schema?: boolean;
 14 | }
 15 | 
 16 | export interface ListRecordsInput {
 17 |   [key: string]: unknown;
 18 |   table: string;
 19 |   maxRecords?: number;
 20 |   view?: string;
 21 |   filterByFormula?: string;
 22 |   sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
 23 |   pageSize?: number;
 24 |   offset?: string;
 25 | }
 26 | 
 27 | export interface GetRecordInput {
 28 |   table: string;
 29 |   recordId: string;
 30 | }
 31 | 
 32 | export interface CreateRecordInput {
 33 |   table: string;
 34 |   fields: Record<string, unknown>;
 35 |   typecast?: boolean;
 36 | }
 37 | 
 38 | export interface UpdateRecordInput {
 39 |   table: string;
 40 |   recordId: string;
 41 |   fields: Record<string, unknown>;
 42 |   typecast?: boolean;
 43 | }
 44 | 
 45 | export interface DeleteRecordInput {
 46 |   table: string;
 47 |   recordId: string;
 48 | }
 49 | 
 50 | export interface SearchRecordsInput {
 51 |   table: string;
 52 |   filterByFormula?: string;
 53 |   sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
 54 |   maxRecords?: number;
 55 |   view?: string;
 56 | }
 57 | 
 58 | // ============================================================================
 59 | // Webhook Management Tool Interfaces
 60 | // ============================================================================
 61 | 
 62 | export interface ListWebhooksInput {
 63 |   cursor?: string;
 64 | }
 65 | 
 66 | export interface CreateWebhookInput {
 67 |   notificationUrl: string;
 68 |   specification?: {
 69 |     options?: {
 70 |       filters?: {
 71 |         dataTypes?: ('tableData' | 'tableSchema')[];
 72 |         recordChangeScope?: string;
 73 |         watchDataInTableIds?: string[];
 74 |       };
 75 |     };
 76 |   };
 77 | }
 78 | 
 79 | export interface DeleteWebhookInput {
 80 |   webhookId: string;
 81 | }
 82 | 
 83 | export interface GetWebhookPayloadsInput {
 84 |   webhookId: string;
 85 |   cursor?: string;
 86 |   limit?: number;
 87 | }
 88 | 
 89 | export interface RefreshWebhookInput {
 90 |   webhookId: string;
 91 | }
 92 | 
 93 | // ============================================================================
 94 | // Schema Discovery Tool Interfaces
 95 | // ============================================================================
 96 | 
 97 | export interface ListBasesInput {
 98 |   offset?: string;
 99 | }
100 | 
101 | export interface GetBaseSchemaInput {
102 |   baseId?: string;
103 | }
104 | 
105 | export interface DescribeTableInput {
106 |   table: string;
107 |   include_sample_data?: boolean;
108 | }
109 | 
110 | export interface ListFieldTypesInput {
111 |   category?: 'basic' | 'advanced' | 'computed';
112 | }
113 | 
114 | export interface GetTableViewsInput {
115 |   table: string;
116 | }
117 | 
118 | // ============================================================================
119 | // Table Management Tool Interfaces
120 | // ============================================================================
121 | 
122 | export interface CreateTableInput {
123 |   name: string;
124 |   description?: string;
125 |   fields: Array<{
126 |     name: string;
127 |     type: string;
128 |     description?: string;
129 |     options?: Record<string, unknown>;
130 |   }>;
131 | }
132 | 
133 | export interface UpdateTableInput {
134 |   table: string;
135 |   name?: string;
136 |   description?: string;
137 | }
138 | 
139 | export interface DeleteTableInput {
140 |   table: string;
141 |   confirmation?: string;
142 | }
143 | 
144 | // ============================================================================
145 | // Field Management Tool Interfaces
146 | // ============================================================================
147 | 
148 | export interface CreateFieldInput {
149 |   table: string;
150 |   name: string;
151 |   type: string;
152 |   description?: string;
153 |   options?: Record<string, unknown>;
154 | }
155 | 
156 | export interface UpdateFieldInput {
157 |   table: string;
158 |   fieldId: string;
159 |   name?: string;
160 |   description?: string;
161 |   options?: Record<string, unknown>;
162 | }
163 | 
164 | export interface DeleteFieldInput {
165 |   table: string;
166 |   fieldId: string;
167 |   confirmation?: string;
168 | }
169 | 
170 | // ============================================================================
171 | // Batch Operations Tool Interfaces
172 | // ============================================================================
173 | 
174 | export interface BatchCreateRecordsInput {
175 |   table: string;
176 |   records: Array<{
177 |     fields: Record<string, unknown>;
178 |   }>;
179 |   typecast?: boolean;
180 | }
181 | 
182 | export interface BatchUpdateRecordsInput {
183 |   table: string;
184 |   records: Array<{
185 |     id: string;
186 |     fields: Record<string, unknown>;
187 |   }>;
188 |   typecast?: boolean;
189 | }
190 | 
191 | export interface BatchDeleteRecordsInput {
192 |   table: string;
193 |   records: Array<{
194 |     id: string;
195 |   }>;
196 | }
197 | 
198 | export interface BatchUpsertRecordsInput {
199 |   table: string;
200 |   records: Array<{
201 |     key_field: string;
202 |     key_value: string;
203 |     fields: Record<string, unknown>;
204 |   }>;
205 |   typecast?: boolean;
206 | }
207 | 
208 | // ============================================================================
209 | // Attachment Management Tool Interfaces
210 | // ============================================================================
211 | 
212 | export interface UploadAttachmentInput {
213 |   table: string;
214 |   recordId: string;
215 |   fieldName: string;
216 |   url: string;
217 |   filename?: string;
218 | }
219 | 
220 | // ============================================================================
221 | // Advanced Views Tool Interfaces
222 | // ============================================================================
223 | 
224 | export interface CreateViewInput {
225 |   table: string;
226 |   name: string;
227 |   type: 'grid' | 'form' | 'calendar' | 'gallery' | 'kanban';
228 |   visibleFieldIds?: string[];
229 |   filterByFormula?: string;
230 |   sort?: Array<{ field: string; direction: 'asc' | 'desc' }>;
231 | }
232 | 
233 | export interface GetViewMetadataInput {
234 |   table: string;
235 |   viewId: string;
236 | }
237 | 
238 | // ============================================================================
239 | // Base Management Tool Interfaces
240 | // ============================================================================
241 | 
242 | export interface CreateBaseInput {
243 |   name: string;
244 |   workspaceId?: string;
245 |   tables?: Array<{
246 |     name: string;
247 |     description?: string;
248 |     fields: Array<{
249 |       name: string;
250 |       type: string;
251 |       options?: Record<string, unknown>;
252 |     }>;
253 |   }>;
254 | }
255 | 
256 | export interface ListCollaboratorsInput {
257 |   baseId?: string;
258 | }
259 | 
260 | export interface ListSharesInput {
261 |   baseId?: string;
262 | }
263 | 
264 | // ============================================================================
265 | // Tool Response Interfaces
266 | // ============================================================================
267 | 
268 | export interface ToolResponse<T = unknown> {
269 |   content: Array<{
270 |     type: 'text' | 'image' | 'resource';
271 |     text?: string;
272 |     data?: T;
273 |     mimeType?: string;
274 |   }>;
275 |   isError?: boolean;
276 | }
277 | 
278 | export interface PaginatedResponse<T> {
279 |   records?: T[];
280 |   offset?: string;
281 | }
282 | 
283 | export interface TableInfo {
284 |   id: string;
285 |   name: string;
286 |   description?: string;
287 |   primaryFieldId: string;
288 |   fields: Array<{
289 |     id: string;
290 |     name: string;
291 |     type: string;
292 |     options?: Record<string, unknown>;
293 |     description?: string;
294 |   }>;
295 |   views: Array<{
296 |     id: string;
297 |     name: string;
298 |     type: string;
299 |   }>;
300 | }
301 | 
302 | export interface RecordInfo {
303 |   id: string;
304 |   fields: Record<string, unknown>;
305 |   createdTime: string;
306 |   commentCount?: number;
307 | }
308 | 
309 | export interface WebhookInfo {
310 |   id: string;
311 |   macSecretBase64: string;
312 |   expirationTime: string;
313 |   notificationUrl: string;
314 |   isHookEnabled: boolean;
315 |   specification: {
316 |     options: {
317 |       filters: {
318 |         dataTypes: string[];
319 |         recordChangeScope?: string;
320 |         watchDataInTableIds?: string[];
321 |       };
322 |     };
323 |   };
324 | }
325 | 
326 | export interface BaseInfo {
327 |   id: string;
328 |   name: string;
329 |   permissionLevel: 'read' | 'comment' | 'edit' | 'create';
330 | }
331 | 
332 | export interface FieldTypeInfo {
333 |   type: string;
334 |   name: string;
335 |   description: string;
336 |   supportedOptions?: string[];
337 |   examples?: Record<string, unknown>[];
338 | }
339 | 
340 | export interface ViewInfo {
341 |   id: string;
342 |   name: string;
343 |   type: 'grid' | 'form' | 'calendar' | 'gallery' | 'kanban' | 'timeline' | 'block';
344 |   visibleFieldIds?: string[];
345 |   filterByFormula?: string;
346 |   sort?: Array<{
347 |     field: string;
348 |     direction: 'asc' | 'desc';
349 |   }>;
350 | }
351 | 
352 | export interface CollaboratorInfo {
353 |   type: 'user' | 'group';
354 |   id: string;
355 |   email?: string;
356 |   name?: string;
357 |   permissionLevel: 'read' | 'comment' | 'edit' | 'create';
358 |   createdTime: string;
359 | }
360 | 
361 | export interface ShareInfo {
362 |   id: string;
363 |   type: 'view' | 'base';
364 |   url: string;
365 |   isPasswordRequired: boolean;
366 |   allowedActions: string[];
367 |   restriction?: {
368 |     dateRange?: {
369 |       startDate?: string;
370 |       endDate?: string;
371 |     };
372 |     allowCommenting?: boolean;
373 |     allowCopyingData?: boolean;
374 |   };
375 | }
376 | 
377 | // ============================================================================
378 | // Complete Tool Schema Definitions
379 | // ============================================================================
380 | 
381 | // Tool schemas are defined in tools-schemas.ts for runtime use
382 | 
383 | // ============================================================================
384 | // Export All Tool Types
385 | // ============================================================================
386 | 
387 | export {
388 |   ListTablesInput,
389 |   ListRecordsInput,
390 |   GetRecordInput,
391 |   CreateRecordInput,
392 |   UpdateRecordInput,
393 |   DeleteRecordInput,
394 |   SearchRecordsInput,
395 |   
396 |   ListWebhooksInput,
397 |   CreateWebhookInput,
398 |   DeleteWebhookInput,
399 |   GetWebhookPayloadsInput,
400 |   RefreshWebhookInput,
401 |   
402 |   BatchCreateRecordsInput,
403 |   BatchUpdateRecordsInput,
404 |   BatchDeleteRecordsInput,
405 |   BatchUpsertRecordsInput,
406 |   
407 |   ToolResponse,
408 |   PaginatedResponse,
409 |   TableInfo,
410 |   RecordInfo,
411 |   WebhookInfo,
412 |   BaseInfo,
413 |   FieldTypeInfo,
414 |   ViewInfo,
415 |   CollaboratorInfo,
416 |   ShareInfo
417 | };
```

--------------------------------------------------------------------------------
/docs/releases/RELEASE_NOTES_v1.6.0.md:
--------------------------------------------------------------------------------

```markdown
  1 | # 🚀 Airtable MCP Server v1.6.0 Release Notes
  2 | 
  3 | **Release Date**: August 15, 2025  
  4 | **Major Update**: Batch Operations, Attachment Management & Advanced Features
  5 | 
  6 | ## 🎯 Overview
  7 | 
  8 | Version 1.6.0 represents another **major expansion** of the Airtable MCP Server, adding powerful batch operations, attachment management, and advanced base management capabilities. This release increases the total tools from 23 to **33 tools**, providing the most comprehensive Airtable API coverage available for AI assistants.
  9 | 
 10 | ## ✨ New Features (10 New Tools)
 11 | 
 12 | ### ⚡ Batch Operations (4 New Tools)
 13 | 
 14 | 1. **`batch_create_records`** - Create up to 10 records simultaneously
 15 |    - Significantly improves performance for bulk data entry
 16 |    - Maintains atomicity - all records created or none
 17 |    - Proper error handling for validation failures
 18 | 
 19 | 2. **`batch_update_records`** - Update up to 10 records at once
 20 |    - Efficient bulk updates with field-level precision
 21 |    - Maintains data integrity across operations
 22 |    - Returns detailed success/failure information
 23 | 
 24 | 3. **`batch_delete_records`** - Delete up to 10 records in one operation
 25 |    - Fast bulk deletion with safety validation
 26 |    - Atomic operation ensures consistency
 27 |    - Detailed deletion confirmation
 28 | 
 29 | 4. **`batch_upsert_records`** - Smart update-or-create operations
 30 |    - Updates existing records or creates new ones based on key fields
 31 |    - Intelligent matching using specified key fields
 32 |    - Optimizes data synchronization workflows
 33 | 
 34 | ### 📎 Attachment Management (1 New Tool)
 35 | 
 36 | 5. **`upload_attachment`** - Attach files from URLs to records
 37 |    - Supports any publicly accessible file URL
 38 |    - Automatic file type detection and validation
 39 |    - Optional custom filename specification
 40 |    - Works with all Airtable-supported file types
 41 | 
 42 | ### 👁️ Advanced View Management (2 New Tools)
 43 | 
 44 | 6. **`create_view`** - Create custom views programmatically
 45 |    - Support for all view types: grid, form, calendar, gallery, kanban, timeline, gantt
 46 |    - Custom field visibility and ordering
 47 |    - Configurable filters and sorts
 48 |    - Automated view setup for workflows
 49 | 
 50 | 7. **`get_view_metadata`** - Detailed view configuration retrieval
 51 |    - Complete view settings and configurations
 52 |    - Filter formulas and sort specifications
 53 |    - Field visibility and ordering information
 54 |    - Perfect for view replication and analysis
 55 | 
 56 | ### 🏢 Base Management (3 New Tools)
 57 | 
 58 | 8. **`create_base`** - Create new Airtable bases
 59 |    - Programmatic base creation with initial table structures
 60 |    - Support for workspace organization
 61 |    - Batch table and field creation
 62 |    - Perfect for template deployment
 63 | 
 64 | 9. **`list_collaborators`** - View base collaboration details
 65 |    - Complete collaborator list with permission levels
 66 |    - User type identification (user, group, etc.)
 67 |    - Permission auditing and management
 68 |    - Security compliance support
 69 | 
 70 | 10. **`list_shares`** - Manage shared view configurations
 71 |     - Public share URLs and settings
 72 |     - Share type and effectiveness status
 73 |     - View and table relationship mapping
 74 |     - Privacy and access control management
 75 | 
 76 | ## 🔄 Enhanced Existing Features
 77 | 
 78 | ### Performance Improvements
 79 | - **Batch Operations**: Up to 10x faster for bulk operations
 80 | - **Error Handling**: More detailed error messages and validation
 81 | - **API Efficiency**: Reduced API calls through intelligent batching
 82 | 
 83 | ### Security Enhancements
 84 | - **Input Validation**: Enhanced parameter validation for all new tools
 85 | - **Permission Checking**: Better handling of permission-restricted operations
 86 | - **Safe Defaults**: Conservative defaults for destructive operations
 87 | 
 88 | ### User Experience
 89 | - **Better Error Messages**: More descriptive error responses
 90 | - **Consistent Interface**: Uniform parameter naming across all tools
 91 | - **Enhanced Documentation**: Detailed examples and use cases
 92 | 
 93 | ## 📊 Tool Count Progression
 94 | 
 95 | | Version | Total Tools | New Features |
 96 | |---------|-------------|--------------|
 97 | | **v1.6.0** | **33** | Batch ops, attachments, advanced views, base mgmt |
 98 | | v1.5.0 | 23 | Schema management |
 99 | | v1.4.0 | 12 | Webhooks |
100 | | v1.2.4 | 5 | Basic CRUD |
101 | 
102 | ## 🛠️ Technical Improvements
103 | 
104 | ### API Coverage
105 | - **Complete Airtable API**: Now covers virtually all public Airtable API endpoints
106 | - **Batch Endpoints**: Full support for Airtable's batch operation limits
107 | - **Metadata API**: Complete integration with Airtable's metadata capabilities
108 | 
109 | ### Architecture
110 | - **Modular Design**: Clean separation of concerns for each tool category
111 | - **Error Resilience**: Improved error handling and recovery
112 | - **Performance Optimized**: Efficient API usage patterns
113 | 
114 | ### Compatibility
115 | - **Backward Compatible**: All v1.5.0 tools unchanged
116 | - **API Limits**: Respects Airtable's rate limits and batch size restrictions
117 | - **Token Scopes**: Graceful handling of insufficient permissions
118 | 
119 | ## 📚 New Capabilities
120 | 
121 | ### For Users
122 | - **Bulk Data Operations**: Efficiently manage large datasets
123 | - **File Management**: Easy attachment handling through URLs
124 | - **Advanced Workflows**: Create complex multi-step processes
125 | - **Collaboration Insights**: Understand base sharing and permissions
126 | - **Template Creation**: Programmatically create standardized bases
127 | 
128 | ### For Developers
129 | - **High-Performance Bulk Ops**: Optimize data synchronization
130 | - **Complete Base Lifecycle**: Full cradle-to-grave base management
131 | - **Advanced View Control**: Programmatic UI customization
132 | - **Security Auditing**: Comprehensive permission monitoring
133 | 
134 | ## 🚀 Getting Started with v1.6.0
135 | 
136 | ### Installation
137 | ```bash
138 | npm install -g @rashidazarang/[email protected]
139 | ```
140 | 
141 | ### New Usage Examples
142 | 
143 | #### Batch Operations
144 | ```javascript
145 | // Create multiple records efficiently
146 | "Create 5 new project records with these details: [project data]"
147 | 
148 | // Update multiple records at once
149 | "Update all records where status is 'pending' to 'in progress'"
150 | 
151 | // Delete multiple records
152 | "Delete these 3 completed tasks: rec123, rec456, rec789"
153 | ```
154 | 
155 | #### Attachment Management
156 | ```javascript
157 | // Attach files to records
158 | "Attach this image https://example.com/image.jpg to the product photo field in record rec123"
159 | 
160 | // Batch create with attachments
161 | "Create a new product record and attach the logo from this URL"
162 | ```
163 | 
164 | #### Advanced Views
165 | ```javascript
166 | // Create custom views
167 | "Create a calendar view for the Events table showing only future events"
168 | 
169 | // Analyze view configurations
170 | "Show me the detailed configuration of the 'Active Projects' view"
171 | ```
172 | 
173 | #### Base Management
174 | ```javascript
175 | // Create new bases
176 | "Create a new base called 'Project Tracker' with tables for Projects, Tasks, and Team Members"
177 | 
178 | // Collaboration insights
179 | "Who has access to this base and what are their permission levels?"
180 | ```
181 | 
182 | ## 🔧 Breaking Changes
183 | 
184 | **None** - v1.6.0 maintains full backward compatibility with all previous versions.
185 | 
186 | ## 🐛 Bug Fixes
187 | 
188 | - **Batch Size Validation**: Proper enforcement of 10-record limits
189 | - **Error Message Clarity**: More descriptive API error responses
190 | - **Permission Handling**: Better graceful degradation for insufficient permissions
191 | - **URL Validation**: Enhanced validation for attachment URLs
192 | 
193 | ## ⚡ Performance Improvements
194 | 
195 | - **Batch Operations**: Up to 10x performance improvement for bulk operations
196 | - **API Efficiency**: Reduced API calls through intelligent batching
197 | - **Memory Usage**: Optimized memory usage for large operations
198 | - **Response Processing**: Faster JSON parsing and response handling
199 | 
200 | ## 🌟 What's Next
201 | 
202 | Based on user feedback and Airtable API evolution:
203 | - Enhanced search and filtering capabilities
204 | - Advanced automation triggers
205 | - Real-time collaboration features
206 | - Performance analytics and monitoring
207 | - Enterprise-grade security features
208 | 
209 | ## 📈 Compatibility & Requirements
210 | 
211 | - **Node.js**: Requires Node.js 14+
212 | - **Airtable API**: Compatible with latest Airtable API version
213 | - **Rate Limits**: Respects Airtable's 5 requests/second limit
214 | - **Token Scopes**: Requires appropriate scopes for advanced features
215 | 
216 | ### Required Scopes for Full Functionality
217 | - `data.records:read` - Read records
218 | - `data.records:write` - Create, update, delete records
219 | - `schema.bases:read` - View schemas and metadata
220 | - `schema.bases:write` - Create/modify tables, fields, views, bases
221 | - `webhook:manage` - Webhook operations (optional)
222 | 
223 | ## 📊 Testing & Quality
224 | 
225 | - **100% Test Coverage**: All 33 tools tested with real API calls
226 | - **Edge Case Handling**: Comprehensive error condition testing
227 | - **Performance Testing**: Batch operation efficiency verification
228 | - **Security Testing**: Permission and validation testing
229 | 
230 | ## 🤝 Community Impact
231 | 
232 | v1.6.0 establishes this MCP server as the definitive Airtable integration for AI assistants, providing:
233 | 
234 | - **Most Comprehensive Coverage**: 33 tools covering entire Airtable API
235 | - **Best Performance**: Intelligent batching and optimization
236 | - **Enterprise Ready**: Advanced collaboration and security features
237 | - **Developer Friendly**: Clean, consistent, well-documented interface
238 | 
239 | ## 🔗 Resources
240 | 
241 | **GitHub**: https://github.com/rashidazarang/airtable-mcp  
242 | **NPM**: https://www.npmjs.com/package/@rashidazarang/airtable-mcp  
243 | **Issues**: https://github.com/rashidazarang/airtable-mcp/issues  
244 | **Documentation**: https://github.com/rashidazarang/airtable-mcp#readme
245 | 
246 | ---
247 | 
248 | 🎉 **Thank you for using Airtable MCP Server v1.6.0!** This release represents the culmination of comprehensive Airtable API integration, providing AI assistants with unprecedented access to Airtable's full feature set through natural language interactions.
```
Page 2/5FirstPrevNextLast