#
tokens: 48988/50000 96/119 files (page 1/4)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 4. Use http://codebase.md/rashidazarang/airtable-mcp?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

--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------

```
18.18.0
```

--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------

```
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf",
  "overrides": [
    {
      "files": "*.md",
      "options": {
        "proseWrap": "always"
      }
    },
    {
      "files": "*.json",
      "options": {
        "printWidth": 80
      }
    }
  ]
}
```

--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------

```javascript
module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
    project: './tsconfig.json',
  },
  env: {
    node: true,
    es2022: true,
    jest: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
    'prettier',
  ],
  plugins: ['@typescript-eslint'],
  ignorePatterns: ['dist/', 'node_modules/', '*.js'],
  rules: {
    // TypeScript specific rules
    '@typescript-eslint/explicit-function-return-type': 'warn',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    '@typescript-eslint/no-non-null-assertion': 'warn',
    
    // General rules
    'no-console': ['warn', { allow: ['warn', 'error'] }],
    'prefer-const': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-template': 'error',
    
    // Code quality
    complexity: ['warn', 10],
    'max-lines': ['warn', 500],
    'max-depth': ['warn', 4],
  },
  overrides: [
    {
      files: ['*.js'],
      parser: 'espree',
      parserOptions: {
        ecmaVersion: 2022,
      },
      extends: ['eslint:recommended'],
      rules: {
        'no-console': 'off',
      },
    },
    {
      files: ['tests/**/*.{js,ts}'],
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        'no-console': 'off',
      },
    },
  ],
};
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Virtual environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Node.js
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
lerna-debug.log
.pnpm-debug.log
.npm
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

# Logs
logs
*.log

# OS specific
.DS_Store
.AppleDouble
.LSOverride
Thumbs.db
ehthumbs.db
Desktop.ini

# IDEs and editors
.idea/
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.sublime-project
*.sublime-workspace

# Personal tokens and secrets
*.pem
*.key
*.env
secrets.json

# Local
temp/
tmp/
.cache/

# Runtime data
*.pid
*.seed
*.pid.lock

# Environment variables
.env.*

# AI Agent Project (new subdirectory)
/ai-agent/

# Test artifacts and temporary files
/test-clone/
*.tmp
*.temp

# Development artifacts
/airtable_simple_v*.js
/airtable_enhanced.js
/airtable_v*.js
*.backup.js

# Development versions and test files
/airtable_mcp_v2.js
/airtable_mcp_v2_oauth.js
/airtable_mcp_v3_advanced.js
/test_*.js
/test_*.sh
/quick_test.sh
/cleanup.sh
/publish-steps.txt

# Development documentation
/DEVELOPMENT.md
/IMPROVEMENT_PROPOSAL.md
/MCP_REVIEW_SUMMARY.md
/CAPABILITY_REPORT.md
/API_DOCUMENTATION.md
/RELEASE_NOTES_*.md

# Infrastructure files (keep for reference but not in main package)
/helm/
/k8s/
/monitoring/
/docker-compose.production.yml
/Dockerfile.production
/.github/workflows/
/.github/ISSUE_TEMPLATE/

# Chrome extension development
/airtable-clipper/

# Package artifacts
*.tgz
/rashidazarang-airtable-mcp-*.tgz

# Claude Code artifacts
/.claude/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Airtable MCP Server

[![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/rashidazarang/airtable-mcp)](https://archestra.ai/mcp-catalog/rashidazarang__airtable-mcp)
[![smithery badge](https://smithery.ai/badge/@rashidazarang/airtable-mcp)](https://smithery.ai/server/@rashidazarang/airtable-mcp)
![Airtable](https://img.shields.io/badge/Airtable-18BFFF?style=for-the-badge&logo=Airtable&logoColor=white)
[![MCP](https://img.shields.io/badge/MCP-3.2.5-blue)](https://github.com/rashidazarang/airtable-mcp)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue)](https://www.typescriptlang.org/)
[![AI Agent](https://img.shields.io/badge/AI_Agent-Enhanced-purple)](https://github.com/rashidazarang/airtable-mcp)
[![Security](https://img.shields.io/badge/Security-Enterprise-green)](https://github.com/rashidazarang/airtable-mcp)
[![Protocol](https://img.shields.io/badge/Protocol-2024--11--05-success)](https://modelcontextprotocol.io/)

🤖 **Revolutionary AI Agent v3.2.5** - Advanced AI-powered Airtable MCP server with **fixed TypeScript architecture**, world-class project organization, comprehensive intelligence capabilities, predictive analytics, and enterprise automation features.

## 🚀 Latest: v3.2.5 - Optional Base ID & Enhanced Multi-Base Support

**Major Improvements** with full backward compatibility:
- 🔓 **Optional Base ID** - Start without specifying a base, discover them using `list_bases` tool
- 🔍 **Enhanced Base Discovery** - New `list_bases` tool fully implemented in TypeScript
- 🎯 **Dynamic Base Selection** - Specify base IDs per tool call, no startup requirement
- ✅ **Fixed Issue #9** - Resolved "base required at startup" limitation
- 🔧 **Improved Governance** - Smart base allowlist handling for multi-base workflows
- 📦 **Full STDIO Support** - Confirmed compatibility with Claude Desktop/Code

## 📋 Previous: v3.2.4 - XSS Security Fix & Complete Protection

**Major Improvements** with full backward compatibility:
- 🔧 **TypeScript Architecture Fixed** - Resolved compilation issues, proper separation of types and runtime code
- 📁 **World-Class Organization** - Restructured project with src/typescript, src/javascript, src/python
- 🔒 **Security Fix Complete** - Fully resolved command injection vulnerability with comprehensive validation
- 🔷 **TypeScript Implementation** - Complete type-safe server with strict validation
- 📘 **Comprehensive Type Definitions** - All 33 tools and 10 AI prompts fully typed
- 🛡️ **Compile-Time Safety** - Catch errors before runtime with advanced type checking
- 🎯 **Developer Experience** - IntelliSense, auto-completion, and refactoring support
- 🔄 **Dual Distribution** - Use with JavaScript or TypeScript, your choice

## 🤖 AI Intelligence Suite

**Complete AI-Powered Intelligence** with enterprise capabilities:
- 🤖 **10 AI Prompt Templates** - Advanced analytics, predictions, and automation
- 🔮 **Predictive Analytics** - Forecasting and trend analysis with confidence intervals
- 🗣️ **Natural Language Processing** - Query your data using human language
- 📊 **Business Intelligence** - Automated insights and recommendations
- 🏗️ **Smart Schema Design** - AI-optimized database architecture
- ⚡ **Workflow Automation** - Intelligent process optimization
- 🔍 **Data Quality Auditing** - Comprehensive quality assessment and fixes
- 📈 **Statistical Analysis** - Advanced analytics with significance testing

## ✨ Features

- 🔍 **Natural Language Queries** - Ask questions about your data in plain English
- 📊 **Full CRUD Operations** - Create, read, update, and delete records
- 🪝 **Webhook Management** - Create and manage webhooks for real-time notifications
- 🏗️ **Advanced Schema Management** - Create tables, fields, and manage base structure
- 🔍 **Base Discovery** - Explore all accessible bases and their schemas
- 🔧 **Field Management** - Add, modify, and remove fields programmatically
- 🔐 **Secure Authentication** - Uses environment variables for credentials
- 🚀 **Easy Setup** - Multiple installation options available
- ⚡ **Fast & Reliable** - Built with Node.js for optimal performance
- 🎯 **33 Powerful Tools** - Complete Airtable API coverage with batch operations
- 📎 **Attachment Management** - Upload files via URLs to attachment fields
- ⚡ **Batch Operations** - Create, update, delete up to 10 records at once
- 👥 **Collaboration Tools** - Manage base collaborators and shared views
- 🤖 **AI Integration** - Prompts and sampling for intelligent data operations
- 🔐 **Enterprise Security** - OAuth2, rate limiting, comprehensive validation

## 📋 Prerequisites

- Node.js 14+ installed on your system
- An Airtable account with a Personal Access Token
- Your Airtable Base ID

## 🚀 Quick Start

### Step 1: Get Your Airtable Credentials

1. **Personal Access Token**: Visit [Airtable Account](https://airtable.com/account) → Create a token with the following scopes:
   - `data.records:read` - Read records from tables
   - `data.records:write` - Create, update, delete records
   - `schema.bases:read` - View table schemas
   - `schema.bases:write` - **New in v1.5.0** - Create/modify tables and fields
   - `webhook:manage` - (Optional) For webhook features

2. **Base ID**: Open your Airtable base and copy the ID from the URL:
   ```
   https://airtable.com/[BASE_ID]/...
   ```

### Step 2: Installation

Choose one of these installation methods:

#### 🔷 TypeScript Users (Recommended for Development)

```bash
# Install with TypeScript support
npm install -g @rashidazarang/airtable-mcp

# For development with types
npm install --save-dev typescript @types/node
```

#### 📦 JavaScript Users (Production Ready)

**Option A: Install via NPM (Recommended)**

```bash
npm install -g @rashidazarang/airtable-mcp
```

**Option B: Clone from GitHub**

```bash
git clone https://github.com/rashidazarang/airtable-mcp.git
cd airtable-mcp
npm install
```

### Step 3: Set Up Environment Variables

Create a `.env` file in your project directory:

```env
AIRTABLE_TOKEN=your_personal_access_token_here
AIRTABLE_BASE_ID=your_base_id_here  # OPTIONAL - can be discovered using list_bases tool
```

**New in v3.2.5**: The `AIRTABLE_BASE_ID` is now **optional**! You can:
- Start without a base ID and use the `list_bases` tool to discover your accessible bases
- Specify base IDs dynamically in each tool call
- Set a default base for convenience (recommended)

**Security Note**: Never commit `.env` files to version control!

### Step 4: Configure Your MCP Client

#### 🔷 TypeScript Configuration (Enhanced Developer Experience)

Add to your Claude Desktop configuration file with TypeScript binary:

**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\\Claude\\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "airtable-typescript": {
      "command": "npx",
      "args": ["@rashidazarang/airtable-mcp"],
      "env": {
        "AIRTABLE_TOKEN": "YOUR_AIRTABLE_TOKEN",
        "AIRTABLE_BASE_ID": "YOUR_BASE_ID",
        "NODE_ENV": "production",
        "LOG_LEVEL": "INFO"
      }
    }
  }
}
```

**Note**: `AIRTABLE_BASE_ID` is optional. Omit it to discover bases using `list_bases` tool.

#### 📦 JavaScript Configuration (Standard)

Add to your Claude Desktop configuration file:

**MacOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "airtable": {
      "command": "npx",
      "args": ["@rashidazarang/airtable-mcp"],
      "env": {
        "AIRTABLE_TOKEN": "YOUR_AIRTABLE_TOKEN",
        "AIRTABLE_BASE_ID": "YOUR_BASE_ID"
      }
    }
  }
}
```

**Note**: `AIRTABLE_BASE_ID` is optional. Omit it to discover bases using `list_bases` tool.

#### Configuration Without Base ID (New!)

Start without specifying a base and discover them dynamically:

```json
{
  "mcpServers": {
    "airtable": {
      "command": "npx",
      "args": ["@rashidazarang/airtable-mcp"],
      "env": {
        "AIRTABLE_TOKEN": "YOUR_AIRTABLE_TOKEN"
      }
    }
  }
}
```

Then use the `list_bases` tool to discover your accessible bases!

### Step 5: Restart Your MCP Client

After configuration, restart Claude Desktop or your MCP client to load the Airtable server.

## 🎯 Usage Examples

Once configured, you can interact with your Airtable data naturally:

### 🔷 TypeScript Development

```typescript
import { 
  AirtableMCPServer, 
  ListRecordsInput, 
  AnalyzeDataPrompt 
} from '@rashidazarang/airtable-mcp/types';

const server = new AirtableMCPServer();

// Type-safe data operations
const params: ListRecordsInput = {
  table: 'Tasks',
  maxRecords: 10,
  filterByFormula: "Status = 'Active'"
};

const records = await server.handleToolCall('list_records', params);

// Type-safe AI analytics
const analysis: AnalyzeDataPrompt = {
  table: 'Sales',
  analysis_type: 'predictive',
  confidence_level: 0.95
};

const insights = await server.handlePromptGet('analyze_data', analysis);
```

### 📦 Natural Language Interactions

**Basic Operations**
```
"List all my accessible Airtable bases"
"Show me all records in the Projects table"
"Create a new task with priority 'High' and due date tomorrow"
"Update the status of task ID rec123 to 'Completed'"
"Delete all records where status is 'Archived'"
"What tables are in my base?"
"Search for records where Status equals 'Active'"
```

**Webhook Operations (v1.4.0+)**
```
"Create a webhook for my table that notifies https://my-app.com/webhook"
"List all active webhooks in my base"
"Show me the recent webhook payloads"
"Delete webhook ach123xyz"
```

**Schema Management (v1.5.0+)**
```
"List all my accessible Airtable bases"
"Show me the complete schema for this base"
"Describe the Projects table with all field details"
"Create a new table called 'Tasks' with Name, Priority, and Due Date fields"
"Add a Status field to the existing Projects table"
"What field types are available in Airtable?"
```

**Batch Operations & Attachments (v1.6.0+)**
```
"Create 5 new records at once in the Tasks table"
"Update multiple records with new status values"
"Delete these 3 records in one operation"
"Attach this image URL to the record's photo field"
"Who are the collaborators on this base?"
"Show me all shared views in this base"
```

## 🛠️ Available Tools (33 Total)

### 📊 Data Operations (7 tools)
| Tool | Description |
|------|-------------|
| `list_tables` | Get all tables in your base with schema information |
| `list_records` | Query records with optional filtering and pagination |
| `get_record` | Retrieve a single record by ID |
| `create_record` | Add new records to any table |
| `update_record` | Modify existing record fields |
| `delete_record` | Remove records from a table |
| `search_records` | Advanced search with Airtable formulas and sorting |

### 🪝 Webhook Management (5 tools)
| Tool | Description |
|------|-------------|
| `list_webhooks` | View all webhooks configured for your base |
| `create_webhook` | Set up real-time notifications for data changes |
| `delete_webhook` | Remove webhook configurations |
| `get_webhook_payloads` | Retrieve webhook notification history |
| `refresh_webhook` | Extend webhook expiration time |

### 🔍 Schema Discovery (6 tools) - **New in v1.5.0**
| Tool | Description |
|------|-------------|
| `list_bases` | List all accessible Airtable bases with permissions |
| `get_base_schema` | Get complete schema information for any base |
| `describe_table` | Get detailed table info including all field specifications |
| `list_field_types` | Reference guide for all available Airtable field types |
| `get_table_views` | List all views for a specific table with configurations |

### 🏗️ Table Management (3 tools) - **New in v1.5.0**
| Tool | Description |
|------|-------------|
| `create_table` | Create new tables with custom field definitions |
| `update_table` | Modify table names and descriptions |
| `delete_table` | Remove tables (with safety confirmation required) |

### 🔧 Field Management (3 tools) - **New in v1.5.0**
| Tool | Description |
|------|-------------|
| `create_field` | Add new fields to existing tables with all field types |
| `update_field` | Modify field properties, names, and options |
| `delete_field` | Remove fields (with safety confirmation required) |

### ⚡ Batch Operations (4 tools) - **New in v1.6.0**
| Tool | Description |
|------|-------------|
| `batch_create_records` | Create up to 10 records at once for better performance |
| `batch_update_records` | Update up to 10 records simultaneously |
| `batch_delete_records` | Delete up to 10 records in a single operation |
| `batch_upsert_records` | Update existing or create new records based on key fields |

### 📎 Attachment Management (1 tool) - **New in v1.6.0**
| Tool | Description |
|------|-------------|
| `upload_attachment` | Attach files from public URLs to attachment fields |

### 👁️ Advanced Views (2 tools) - **New in v1.6.0**
| Tool | Description |
|------|-------------|
| `create_view` | Create new views (grid, form, calendar, etc.) with custom configurations |
| `get_view_metadata` | Get detailed view information including filters and sorts |

### 🏢 Base Management (3 tools) - **New in v1.6.0**
| Tool | Description |
|------|-------------|
| `create_base` | Create new Airtable bases with initial table structures |
| `list_collaborators` | View base collaborators and their permission levels |
| `list_shares` | List shared views and their public configurations |

### 🤖 AI Intelligence Suite (10 prompts) - **New in v3.0.0**
| Prompt | Description | Enterprise Features |
|--------|-------------|-------------------|
| `analyze_data` | Advanced statistical analysis with ML insights | Confidence intervals, anomaly detection |
| `create_report` | Intelligent report generation with recommendations | Multi-stakeholder customization, ROI analysis |
| `data_insights` | Business intelligence and pattern discovery | Cross-table correlations, predictive indicators |
| `optimize_workflow` | AI-powered automation recommendations | Change management, implementation roadmaps |
| `smart_schema_design` | Database optimization with best practices | Compliance-aware (GDPR, HIPAA), scalability planning |
| `data_quality_audit` | Comprehensive quality assessment and fixes | Automated remediation, governance frameworks |
| `predictive_analytics` | Forecasting and trend prediction | Multiple algorithms, uncertainty quantification |
| `natural_language_query` | Process human questions intelligently | Context awareness, confidence scoring |
| `smart_data_transformation` | AI-assisted data processing | Quality rules, audit trails, optimization |
| `automation_recommendations` | Workflow optimization suggestions | Technical feasibility, cost-benefit analysis |

## 🔧 Advanced Configuration

### Using with Smithery Cloud

For cloud-hosted MCP servers:

```json
{
  "mcpServers": {
    "airtable": {
      "command": "npx",
      "args": [
        "@smithery/cli",
        "run",
        "@rashidazarang/airtable-mcp",
        "--token",
        "YOUR_TOKEN",
        "--base",
        "YOUR_BASE_ID"
      ]
    }
  }
}
```

### Direct Node.js Execution

If you cloned the repository:

```json
{
  "mcpServers": {
    "airtable": {
      "command": "node",
      "args": [
        "/path/to/airtable-mcp/airtable_simple.js",
        "--token",
        "YOUR_TOKEN",
        "--base",
        "YOUR_BASE_ID"
      ]
    }
  }
}
```

## 🧪 Testing

### 🔷 TypeScript Testing

Run the comprehensive TypeScript test suite:

```bash
# Install dependencies first
npm install

# Run TypeScript type checking
npm run test:types

# Run full TypeScript test suite
npm run test:ts

# Build and test TypeScript server
npm run build
npm run start:ts
```

### 📦 JavaScript Testing

Run the comprehensive test suite to verify all 33 tools:

```bash
# Set environment variables first
export AIRTABLE_TOKEN=your_token
export AIRTABLE_BASE_ID=your_base_id

# Start the server
node airtable_simple.js &

# Run comprehensive tests (v1.6.0+)
./test_v1.6.0_comprehensive.sh
```

The TypeScript test suite validates:
- **Type Safety**: Compile-time validation of all interfaces
- **Enterprise Testing**: 33 tools with strict type checking
- **AI Prompt Validation**: All 10 AI templates with proper typing
- **Error Handling**: Type-safe error management
- **Performance**: Concurrent operations with type safety
- **Integration**: Full MCP protocol compliance

The JavaScript test suite validates:
- All 33 tools with real API calls
- Complete CRUD operations
- Advanced schema management
- Batch operations (create/update/delete multiple records)
- Attachment management via URLs
- Advanced view creation and metadata
- Base management and collaboration tools
- Webhook management
- Error handling and edge cases
- Security verification
- 100% test coverage

## 🐛 Troubleshooting

### "Connection Refused" Error
- Ensure the MCP server is running
- Check that port 8010 is not blocked
- Restart your MCP client

### "Invalid Token" Error
- Verify your Personal Access Token is correct
- Check that the token has the required scopes
- Ensure no extra spaces in your credentials

### "Base Not Found" Error
- Confirm your Base ID is correct
- Check that your token has access to the base

### Port Conflicts
If port 8010 is in use:
```bash
lsof -ti:8010 | xargs kill -9
```

## 📚 Documentation

### 🔷 TypeScript Documentation
- 📘 [TypeScript Examples](./examples/typescript/) - Complete type-safe usage examples
- 🏗️ [Type Definitions](./types/) - Comprehensive type definitions for all features
- 🧪 [TypeScript Testing](./src/test-suite.ts) - Enterprise-grade testing framework

### 📦 General Documentation  
- 🎆 [Release Notes v3.1.0](./RELEASE_NOTES_v3.1.0.md) - **Latest TypeScript release**
- [Release Notes v1.6.0](./RELEASE_NOTES_v1.6.0.md) - Major feature release
- [Release Notes v1.5.0](./RELEASE_NOTES_v1.5.0.md)
- [Release Notes v1.4.0](./RELEASE_NOTES_v1.4.0.md)
- [Detailed Setup Guide](./CLAUDE_INTEGRATION.md)
- [Development Guide](./DEVELOPMENT.md)
- [Security Notice](./SECURITY_NOTICE.md)

## 📦 Version History

- **v3.1.0** (2025-08-16) - 🔷 **TypeScript Support**: Enterprise-grade type safety, comprehensive type definitions, dual JS/TS distribution
- **v3.0.0** (2025-08-16) - 🤖 **Revolutionary AI Agent**: 10 intelligent prompts, predictive analytics, natural language processing
- **v2.2.3** (2025-08-16) - 🔒 **Security release**: Final XSS vulnerability fixes and enhanced validation
- **v2.2.0** (2025-08-16) - 🏆 **Major release**: Complete MCP 2024-11-05 protocol implementation
- **v1.6.0** (2025-08-15) - 🎆 **Major release**: Added batch operations & attachment management (33 total tools)
- **v1.5.0** (2025-08-15) - Added comprehensive schema management (23 total tools)
- **v1.4.0** (2025-08-14) - Added webhook support and enhanced CRUD operations (12 tools)
- **v1.2.4** (2025-08-12) - Security fixes and stability improvements
- **v1.2.3** (2025-08-11) - Bug fixes and error handling
- **v1.2.2** (2025-08-10) - Initial stable release

## 📂 Project Structure

```
airtable-mcp/
├── src/                    # Source code
│   ├── index.js           # Main entry point
│   ├── typescript/        # TypeScript implementation
│   ├── javascript/        # JavaScript implementation
│   └── python/            # Python implementation
├── dist/                  # Compiled TypeScript output
├── docs/                  # Documentation
│   ├── guides/           # User guides
│   └── releases/         # Release notes
├── tests/                # Test files
├── examples/             # Usage examples
└── types/                # TypeScript type definitions
```

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

## 📄 License

MIT License - see [LICENSE](./LICENSE) file for details

## 🙏 Acknowledgments

- Built for the [Model Context Protocol](https://modelcontextprotocol.io/)
- Powered by [Airtable API](https://airtable.com/developers/web/api/introduction)
- Compatible with [Claude Desktop](https://claude.ai/) and other MCP clients

## 📮 Support

- **Issues**: [GitHub Issues](https://github.com/rashidazarang/airtable-mcp/issues)
- **Discussions**: [GitHub Discussions](https://github.com/rashidazarang/airtable-mcp/discussions)

---

**Version**: 3.2.4 | **Status**: 🔷 TypeScript Fixed + 🤖 AI Agent | **MCP Protocol**: 2024-11-05 Complete | **Type Safety**: Enterprise-Grade | **Intelligence**: 10 AI Prompts | **Security**: Fully Patched (XSS Fixed) | **Last Updated**: September 9, 2025

```

--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------

```markdown
# Contributing to Airtable MCP

Thank you for your interest in contributing to Airtable MCP! This guide will help you get started with contributing to this project.

## Development Setup

1. **Clone the repository**:
   ```bash
   git clone https://github.com/rashidazarang/airtable-mcp.git
   cd airtable-mcp
   ```

2. **Install dependencies**:
   ```bash
   pip install -r requirements.txt
   ```

3. **Environment setup**:
   Create a `.env` file in the root directory with your Airtable API token:
   ```
   AIRTABLE_PERSONAL_ACCESS_TOKEN=your_token_here
   AIRTABLE_BASE_ID=optional_default_base_id
   ```

## Running the Server

You can run the server directly with Python:

```bash
python3.10 inspector_server.py --token "your_token" --base "your_base_id"
```

Or through the Node.js wrapper:

```bash
node index.js --token "your_token" --base "your_base_id"
```

## Testing

Run the test client to verify your Airtable API access:

```bash
python3.10 test_client.py
```

## Pull Request Process

1. **Fork the Repository** on GitHub.

2. **Create a Branch** for your feature or bugfix.

3. **Make Changes** according to the project style guidelines.

4. **Test Thoroughly** to ensure your changes work as expected.

5. **Document Changes** in the README.md if necessary.

6. **Submit a Pull Request** to the main repository.

## Coding Guidelines

- Follow Python PEP 8 style guidelines
- Write docstrings for all functions, classes, and modules
- Include type hints for function parameters and return values
- Write clear commit messages

## Adding New Tools

When adding new Airtable API tools:

1. Add the tool function to `inspector_server.py` using the `@app.tool()` decorator
2. Define clear parameter and return types
3. Provide a descriptive docstring for the tool
4. Update the inspector.py file to include the new tool in the JSON schema
5. Add error handling for API requests
6. Update the README.md to document the new tool

## License

By contributing to this project, you agree that your contributions will be licensed under the project's MIT License. 
```

--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------

```markdown
# 🤝 Contributor Covenant Code of Conduct

## 🎯 Our Pledge

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community, while working together towards our **100/100 Trust Score** goal.

## 📋 Our Standards

Examples of behavior that contributes to a positive environment for our community include:

### ✅ Positive Behaviors
- **🤝 Respectful Communication**: Using welcoming and inclusive language
- **🎯 Constructive Feedback**: Providing and gracefully accepting constructive criticism
- **🙏 Empathy**: Showing empathy towards other community members
- **🔒 Security Focus**: Prioritizing security and responsible disclosure
- **📚 Knowledge Sharing**: Helping others learn and grow
- **🚀 Quality Commitment**: Contributing to our Trust Score improvement goals
- **🌟 Recognition**: Acknowledging others' contributions and efforts
- **🔧 Solution-Oriented**: Focusing on what is best for the overall community

### ❌ Unacceptable Behaviors
- **💬 Harassment**: Trolling, insulting/derogatory comments, personal or political attacks
- **📧 Privacy Violations**: Publishing others' private information without permission
- **🔓 Security Violations**: Publicly disclosing security vulnerabilities before responsible disclosure
- **🎯 Scope Creep**: Other conduct which could reasonably be considered inappropriate in a professional setting
- **📊 Spam**: Excessive self-promotion or off-topic content
- **🚫 Discrimination**: Any form of discrimination or exclusion based on protected characteristics

## 🛡️ Security-Specific Guidelines

Given our focus on achieving a **100/100 Trust Score**, we have additional guidelines around security:

### 🔒 Responsible Disclosure
- Report security vulnerabilities privately through appropriate channels
- Do not publicly disclose vulnerabilities until fixes are available
- Follow coordinated disclosure timelines with maintainers

### 🛡️ Security Discussions
- Keep security discussions constructive and solution-focused
- Avoid fear-mongering or exaggerating security issues
- Provide evidence-based security recommendations

## 📊 Trust Score Community Standards

Our community is committed to building the most trusted MCP server for Airtable:

### 🎯 Quality Standards
- **🧪 Testing**: All contributions include appropriate tests
- **📚 Documentation**: Clear documentation accompanies code changes
- **🔍 Code Review**: Constructive and thorough code reviews
- **📈 Continuous Improvement**: Regular updates and enhancements

### 🤝 Collaboration Standards
- **💡 Innovation**: Encouraging creative solutions and new ideas
- **🔄 Iteration**: Embracing feedback and iterative improvement
- **🌐 Inclusivity**: Welcoming contributors of all skill levels
- **📊 Transparency**: Open communication about goals and progress

## 🚀 Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

### 👥 Leadership Team
- **Primary Maintainer**: [@rashidazarang](https://github.com/rashidazarang)
- **Security Team**: security@[domain]
- **Community Moderators**: [to be appointed as community grows]

### 🔧 Enforcement Powers
Community leaders have the right and responsibility to:
- Remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions
- Temporarily or permanently ban contributors for inappropriate behaviors
- Communicate expectations and consequences clearly

## 📏 Scope

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include:

### 📍 Community Spaces
- **GitHub Repository**: Issues, PRs, discussions, and project boards
- **Communication Channels**: Discord, Slack, or other official channels
- **Documentation**: Wiki, docs site, and README files
- **Events**: Conferences, meetups, and online presentations

### 🌐 Public Representation
- Using an official e-mail address
- Posting via an official social media account
- Acting as an appointed representative at online or offline events
- Speaking about the project in interviews or presentations

## 📞 Reporting Guidelines

### 🚨 How to Report
If you experience or witness unacceptable behavior, or have any other concerns, please report it by contacting the community leaders:

- **General Issues**: conduct@[domain]
- **Security Issues**: security@[domain]
- **Direct Contact**: [@rashidazarang](https://github.com/rashidazarang)
- **Anonymous Reporting**: [to be set up as community grows]

### 📝 What to Include
When reporting, please include:
- Your contact information (if comfortable sharing)
- Details of the incident, including:
  - When and where it occurred
  - What happened
  - Who was involved
  - Any available evidence (screenshots, links, etc.)
- Any additional context that would be helpful

### ⚡ Response Timeline
- **Acknowledgment**: Within 24 hours
- **Initial Review**: Within 48 hours
- **Investigation**: 1-7 days (depending on complexity)
- **Resolution**: Varies based on the situation
- **Follow-up**: Ongoing as needed

## 🔧 Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

### 1. 📝 Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

### 2. ⚠️ Warning
**Community Impact**: A violation through a single incident or series of actions.

**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

### 3. ⏸️ Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

### 4. 🚫 Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the community.

## 🔄 Appeals Process

### 📝 How to Appeal
If you believe you have been unfairly sanctioned, you may appeal by:
1. Contacting the community leaders at appeals@[domain]
2. Providing a detailed explanation of why you believe the action was unfair
3. Including any relevant evidence or context
4. Waiting for review and response

### ⏱️ Appeal Timeline
- **Review Period**: 7-14 days
- **Decision**: Final decisions will be communicated clearly
- **Implementation**: Changes take effect immediately upon decision

## 🙏 Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].

## 🎯 Our Commitment

As we work towards our **100/100 Trust Score** goal, we recognize that trust extends beyond technical excellence to include community trust. This Code of Conduct is our commitment to maintaining a community that reflects the same high standards of security, quality, and reliability that we strive for in our code.

Together, we're not just building software – we're building a trusted community that makes the entire MCP ecosystem stronger. 🚀

---

**Last Updated**: August 2025
**Version**: 1.0
**Contact**: conduct@[domain]

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
```

--------------------------------------------------------------------------------
/types/typescript/airtable-mcp-server.d.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
export declare function start(): Promise<void>;

```

--------------------------------------------------------------------------------
/examples/claude_config.json:
--------------------------------------------------------------------------------

```json
{
  "airtable_token": "YOUR_AIRTABLE_TOKEN",
  "base_id": "YOUR_BASE_ID"
} 
```

--------------------------------------------------------------------------------
/examples/claude_simple_config.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "airtable": {
      "url": "http://localhost:8010/mcp"
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/python/airtable_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
"""
Airtable MCP - Airtable integration for AI via Model Context Protocol
"""

__version__ = "0.1.0" 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------

```markdown
---
name: Custom issue template
about: Describe this issue template's purpose here.
title: ''
labels: ''
assignees: ''

---



```

--------------------------------------------------------------------------------
/types/typescript/tools-schemas.d.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Runtime tool schemas for Airtable MCP Server
 */
import type { ToolSchema } from './index';
export declare const COMPLETE_TOOL_SCHEMAS: ToolSchema[];

```

--------------------------------------------------------------------------------
/types/typescript/prompt-templates.d.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Runtime AI prompt templates for Airtable MCP Server
 */
import type { PromptSchema } from './index';
export declare const AI_PROMPT_TEMPLATES: Record<string, PromptSchema>;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/index.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerAllTools(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/query.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerQueryTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/create.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerCreateTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/update.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerUpdateTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/upsert.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerUpsertTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/describe.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerDescribeTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/webhooks.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerWebhookTools(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/listExceptions.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerExceptionsTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/listGovernance.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
export declare function registerGovernanceTool(server: McpServer, ctx: AppContext): void;

```

--------------------------------------------------------------------------------
/types/typescript/app/tools/handleError.d.ts:
--------------------------------------------------------------------------------

```typescript
import { AppContext } from '../context';
export declare function handleToolError(toolName: string, error: unknown, ctx: AppContext): {
    isError: boolean;
    content: {
        type: "text";
        text: string;
    }[];
};

```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
airtable-python-wrapper>=0.15.3
anthropic>=0.19.1
argparse>=1.4.0
python-dotenv>=1.0.0
pydantic>=2.4.2
# MCP core will be installed by Smithery during deployment
requests>=2.31.0
typing-extensions>=4.7.1
websockets>=11.0.3
mcp>=1.4.1 
```

--------------------------------------------------------------------------------
/examples/windsurf_mcp_config.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "AIRTABLE": {
      "command": "npx",
      "args": [
        "-y",
        "@smithery/cli@latest",
        "run",
        "@rashidazarang/airtable-mcp",
        "--token",
        "YOUR_AIRTABLE_TOKEN",
        "--base",
        "YOUR_BASE_ID"
      ]
    }
  }
} 
```

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

```typescript
/**
 * Airtable MCP Server - Main Export
 *
 * This module exports the main server functionality for programmatic use.
 * For CLI usage, use the bin/airtable-mcp.js executable.
 */
export { start } from './airtable-mcp-server';
export * from './errors';
export type { AppConfig, AirtableAuthConfig, LogLevel } from './app/config';
export type { AppContext } from './app/context';

```

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

```typescript
import { AppConfig } from './config';
import { AirtableClient } from './airtable-client';
import { GovernanceService } from './governance';
import { ExceptionStore } from './exceptions';
import { Logger } from './logger';

export interface AppContext {
  config: AppConfig;
  logger: Logger;
  airtable: AirtableClient;
  governance: GovernanceService;
  exceptions: ExceptionStore;
}

```

--------------------------------------------------------------------------------
/types/typescript/app/context.d.ts:
--------------------------------------------------------------------------------

```typescript
import { AppConfig } from './config';
import { AirtableClient } from './airtable-client';
import { GovernanceService } from './governance';
import { ExceptionStore } from './exceptions';
import { Logger } from './logger';
export interface AppContext {
    config: AppConfig;
    logger: Logger;
    airtable: AirtableClient;
    governance: GovernanceService;
    exceptions: ExceptionStore;
}

```

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

```typescript
/**
 * Airtable MCP Server - Main Export
 *
 * This module exports the main server functionality for programmatic use.
 * For CLI usage, use the bin/airtable-mcp.js executable.
 */

export { start } from './airtable-mcp-server';
export * from './errors';

// Re-export types for consumers
export type { AppConfig, AirtableAuthConfig, LogLevel } from './app/config';
export type { AppContext } from './app/context';

```

--------------------------------------------------------------------------------
/types/typescript/app/rateLimiter.d.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Lightweight token-based rate limiter to enforce Airtable quotas.
 * Maintains per-key queues to preserve ordering and fairness.
 */
export declare class RateLimiter {
    private readonly minIntervalMs;
    private readonly lockByKey;
    private readonly nextAvailableByKey;
    constructor({ maxRequestsPerSecond }: {
        maxRequestsPerSecond: number;
    });
    schedule(key: string): Promise<void>;
}

```

--------------------------------------------------------------------------------
/examples/example-tasks-update.json:
--------------------------------------------------------------------------------

```json
[
  {
    "id": "rec1qeTzIUy1p8DF5",
    "fields": {
      "Status": "Completed",
      "Description": "Implement the new feature requested by the client (UPDATED)"
    }
  },
  {
    "id": "recA443jGkhk4fe8B",
    "fields": {
      "Status": "Completed",
      "Priority": "High"
    }
  },
  {
    "id": "recvMTGZYKi8Dcds4",
    "fields": {
      "Status": "In Progress",
      "Description": "Write comprehensive documentation for the project (IN PROGRESS)"
    }
  }
] 
```

--------------------------------------------------------------------------------
/types/typescript/app/config.d.ts:
--------------------------------------------------------------------------------

```typescript
import { GovernanceSnapshot } from './types';
export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
export interface AirtableAuthConfig {
    personalAccessToken: string;
    patHash: string;
    defaultBaseId?: string;
    allowedBases: string[];
}
export interface AppConfig {
    version: string;
    auth: AirtableAuthConfig;
    governance: GovernanceSnapshot;
    logLevel: LogLevel;
    exceptionQueueSize: number;
}
export declare function loadConfig(): AppConfig;

```

--------------------------------------------------------------------------------
/types/typescript/app/logger.d.ts:
--------------------------------------------------------------------------------

```typescript
import { LogLevel } from './config';
export type LogMetadata = Record<string, unknown>;
export declare class Logger {
    private readonly level;
    private readonly context;
    constructor(level: LogLevel, context?: LogMetadata);
    child(context: LogMetadata): Logger;
    error(message: string, metadata?: LogMetadata): void;
    warn(message: string, metadata?: LogMetadata): void;
    info(message: string, metadata?: LogMetadata): void;
    debug(message: string, metadata?: LogMetadata): void;
    private log;
}

```

--------------------------------------------------------------------------------
/types/typescript/app/exceptions.d.ts:
--------------------------------------------------------------------------------

```typescript
import { Logger } from './logger';
import { ListExceptionsInput, ListExceptionsOutput } from './types';
import { AirtableBrainError } from '../errors';
export declare class ExceptionStore {
    private readonly capacity;
    private readonly items;
    private readonly logger;
    constructor(capacity: number, logger: Logger);
    record(error: AirtableBrainError, summary: string, details?: string, proposedFix?: Record<string, unknown>): void;
    list(params: ListExceptionsInput): ListExceptionsOutput;
    private parseCursor;
}

```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------

```markdown
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { governanceOutputSchema } from '../types';
import { AppContext } from '../context';

export function registerGovernanceTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'list_governance',
    {
      description: 'Return governance allow-lists and PII masking policies.',
      outputSchema: governanceOutputSchema.shape
    },
    async () => {
      const snapshot = ctx.governance.getSnapshot();
      return {
        structuredContent: snapshot,
        content: [] as const
      };
    }
  );
}

```

--------------------------------------------------------------------------------
/examples/typescript/claude-desktop-config.json:
--------------------------------------------------------------------------------

```json
{
  "mcpServers": {
    "airtable-typescript": {
      "command": "npx",
      "args": [
        "@rashidazarang/airtable-mcp",
        "--token",
        "YOUR_AIRTABLE_TOKEN",
        "--base", 
        "YOUR_BASE_ID"
      ],
      "env": {
        "NODE_ENV": "production",
        "LOG_LEVEL": "INFO"
      }
    },
    "airtable-typescript-dev": {
      "command": "npm",
      "args": ["run", "start:ts"],
      "cwd": "/path/to/your/airtable-mcp",
      "env": {
        "AIRTABLE_TOKEN": "YOUR_AIRTABLE_TOKEN",
        "AIRTABLE_BASE_ID": "YOUR_BASE_ID",
        "NODE_ENV": "development",
        "LOG_LEVEL": "DEBUG"
      }
    }
  }
}
```

--------------------------------------------------------------------------------
/types/typescript/app/governance.d.ts:
--------------------------------------------------------------------------------

```typescript
import { GovernanceSnapshot } from './types';
type Operation = GovernanceSnapshot['allowedOperations'][number];
export declare class GovernanceService {
    private readonly snapshot;
    private readonly tablesByBase;
    constructor(snapshot: GovernanceSnapshot);
    ensureBaseAllowed(baseId: string): void;
    ensureOperationAllowed(operation: Operation): void;
    ensureTableAllowed(baseId: string, table: string): void;
    listPiiPolicies(baseId: string, table: string): Array<{
        field: string;
        policy: string;
    }>;
    getSnapshot(): GovernanceSnapshot;
    isTableAllowed(baseId: string, table: string): boolean;
    private buildTableIndex;
}
export {};

```

--------------------------------------------------------------------------------
/examples/example-tasks.json:
--------------------------------------------------------------------------------

```json
[
  {
    "id": "rec1qeTzIUy1p8DF5",
    "Name": "Add new feature",
    "Description": "Implement the new feature requested by the client",
    "Status": "In Progress",
    "Priority": "Medium",
    "DueDate": "2024-01-15"
  },
  {
    "id": "recA443jGkhk4fe8B",
    "Name": "Fix login bug",
    "Description": "Users are experiencing issues with the login process",
    "Status": "In Progress",
    "Priority": "Critical",
    "DueDate": "2023-11-15"
  },
  {
    "id": "recvMTGZYKi8Dcds4",
    "Name": "Complete project documentation",
    "Description": "Write comprehensive documentation for the project",
    "Status": "In Progress",
    "Priority": "High",
    "DueDate": "2023-12-31"
  }
]
```

--------------------------------------------------------------------------------
/bin/airtable-mcp.js:
--------------------------------------------------------------------------------

```javascript
#!/usr/bin/env node

const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');

const distServer = path.join(__dirname, '..', 'dist', 'typescript', 'airtable-mcp-server.js');

if (!fs.existsSync(distServer)) {
  console.error('Airtable MCP: compiled server not found.');
  console.error('Run `npm install && npm run build` and try again.');
  process.exit(1);
}

const args = process.argv.slice(2);
const child = spawn(process.execPath, [distServer, ...args], {
  stdio: 'inherit',
  env: process.env,
});

child.on('close', (code) => process.exit(code));

process.on('SIGINT', () => child.kill('SIGINT'));
process.on('SIGTERM', () => child.kill('SIGTERM'));

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import {
  ListExceptionsInput,
  listExceptionsInputSchema,
  listExceptionsOutputSchema
} from '../types';
import { AppContext } from '../context';

export function registerExceptionsTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'list_exceptions',
    {
      description: 'List recent exceptions and remediation proposals.',
      inputSchema: listExceptionsInputSchema.shape,
      outputSchema: listExceptionsOutputSchema.shape
    },
    async (args: ListExceptionsInput) => {
      const snapshot = ctx.exceptions.list(args);
      return {
        structuredContent: snapshot,
        content: [] as const
      };
    }
  );
}

```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]

**Smartphone (please complete the following information):**
 - Device: [e.g. iPhone6]
 - OS: [e.g. iOS8.1]
 - Browser [e.g. stock browser, safari]
 - Version [e.g. 22]

**Additional context**
Add any other context about the problem here.

```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  roots: ['<rootDir>/src', '<rootDir>/tests'],
  testMatch: [
    '**/__tests__/**/*.+(ts|tsx|js)',
    '**/?(*.)+(spec|test).+(ts|tsx|js)',
  ],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest',
  },
  collectCoverageFrom: [
    'src/**/*.{js,ts}',
    '!src/**/*.d.ts',
    '!src/**/index.{js,ts}',
    '!src/**/*.test.{js,ts}',
    '!src/**/*.spec.{js,ts}',
  ],
  coverageDirectory: 'coverage',
  coverageReporters: ['text', 'lcov', 'html'],
  coverageThreshold: {
    global: {
      branches: 70,
      functions: 70,
      lines: 70,
      statements: 70,
    },
  },
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@types/(.*)$': '<rootDir>/types/$1',
  },
  setupFilesAfterEnv: ['<rootDir>/tests/setup.js'],
  testTimeout: 10000,
  verbose: true,
};
```

--------------------------------------------------------------------------------
/src/python/setup.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3

from setuptools import setup, find_packages

with open("README.md", "r", encoding="utf-8") as fh:
    long_description = fh.read()

with open("requirements.txt", "r", encoding="utf-8") as req_file:
    requirements = req_file.read().splitlines()

setup(
    name="airtable-mcp",
    version="1.1.0",
    author="Rashid Azarang",
    author_email="[email protected]",
    description="Airtable MCP for AI tools - updated to work with MCP SDK 1.4.1+",
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/rashidazarang/airtable-mcp",
    packages=find_packages(),
    install_requires=requirements,
    classifiers=[
        "Programming Language :: Python :: 3.10",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires=">=3.10",
    include_package_data=True,
) 
```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import { registerListBasesTool } from './listBases';
import { registerDescribeTool } from './describe';
import { registerQueryTool } from './query';
import { registerGovernanceTool } from './listGovernance';
import { registerExceptionsTool } from './listExceptions';
import { registerCreateTool } from './create';
import { registerUpdateTool } from './update';
import { registerUpsertTool } from './upsert';
import { registerWebhookTools } from './webhooks';

export function registerAllTools(server: McpServer, ctx: AppContext): void {
  registerListBasesTool(server, ctx);
  registerDescribeTool(server, ctx);
  registerQueryTool(server, ctx);
  registerGovernanceTool(server, ctx);
  registerExceptionsTool(server, ctx);
  registerCreateTool(server, ctx);
  registerUpdateTool(server, ctx);
  registerUpsertTool(server, ctx);
  registerWebhookTools(server, ctx);
}

```

--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM python:3.10-slim

WORKDIR /app

# Install Node.js for the NPX functionality
RUN apt-get update && \
    apt-get install -y curl gnupg && \
    curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get install -y nodejs && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# Copy package files first (for better layer caching)
COPY package.json /app/
COPY package-lock.json /app/

# Install Node.js dependencies
RUN npm install

# Copy Python requirements and install
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt

# Copy source code
COPY index.js /app/
COPY inspector.py /app/
COPY inspector_server.py /app/
COPY airtable_mcp/ /app/airtable_mcp/

# Set environment variables
ENV NODE_ENV=production
ENV PYTHONUNBUFFERED=1

# Expose the port the server might run on
EXPOSE 3000

# Start the server in STDIO mode by default
# Smithery will override this with their own command
CMD ["python3", "inspector_server.py"] 
```

--------------------------------------------------------------------------------
/types/typescript/app/tools/listBases.d.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import { z } from 'zod';
declare const listBasesOutputSchema: z.ZodObject<{
    bases: z.ZodArray<z.ZodObject<{
        id: z.ZodString;
        name: z.ZodString;
        permissionLevel: z.ZodOptional<z.ZodString>;
    }, "strip", z.ZodTypeAny, {
        id: string;
        name: string;
        permissionLevel?: string | undefined;
    }, {
        id: string;
        name: string;
        permissionLevel?: string | undefined;
    }>, "many">;
}, "strip", z.ZodTypeAny, {
    bases: {
        id: string;
        name: string;
        permissionLevel?: string | undefined;
    }[];
}, {
    bases: {
        id: string;
        name: string;
        permissionLevel?: string | undefined;
    }[];
}>;
export type ListBasesOutput = z.infer<typeof listBasesOutputSchema>;
export declare function registerListBasesTool(server: McpServer, ctx: AppContext): void;
export {};

```

--------------------------------------------------------------------------------
/examples/python_debug_patch.txt:
--------------------------------------------------------------------------------

```
# Add proper error handling
import traceback
import sys

# Override the default error handlers to format errors as proper JSON
def handle_exceptions(func):
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except Exception as e:
            error_trace = traceback.format_exc()
            sys.stderr.write(f"Error in MCP handler: {str(e)}\n{error_trace}\n")
            # Return a properly formatted JSON error
            return {"error": {"code": -32000, "message": str(e)}}
    return wrapper

# Apply the decorator to all RPC methods
original_rpc_method = app.rpc_method
def patched_rpc_method(*args, **kwargs):
    def decorator(func):
        wrapped_func = handle_exceptions(func)
        return original_rpc_method(*args, **kwargs)(wrapped_func)
    return decorator

# Then add this line right before creating the FastMCP instance:
# Replace app.rpc_method with our patched version
app.rpc_method = patched_rpc_method 
```

--------------------------------------------------------------------------------
/types/typescript/test-suite.d.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * TypeScript Test Suite for Airtable MCP Server
 * Comprehensive type-safe testing with enterprise validation
 */
interface TestResult {
    name: string;
    passed: boolean;
    error?: string;
    duration: number;
}
interface TestSuite {
    name: string;
    tests: TestResult[];
    totalPassed: number;
    totalFailed: number;
    totalDuration: number;
}
declare class TypeScriptTestRunner {
    private results;
    runTest(name: string, testFn: () => Promise<void>): Promise<TestResult>;
    runSuite(suiteName: string, tests: Array<{
        name: string;
        fn: () => Promise<void>;
    }>): Promise<TestSuite>;
    generateReport(): void;
}
declare class MockAirtableMCPServer {
    initialize(): Promise<any>;
    handleToolCall(name: string, params: Record<string, unknown>): Promise<any>;
    handlePromptGet(_name: string, _args: Record<string, unknown>): Promise<any>;
}
declare function runAllTests(): Promise<void>;
export { TypeScriptTestRunner, MockAirtableMCPServer, runAllTests, TestResult, TestSuite };

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "declaration": true,
    "declarationDir": "./types",
    "sourceMap": true,
    "removeComments": false,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": false,
    "noUncheckedIndexedAccess": true,
    "types": []
  },
  "include": [
    "src/typescript/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "types",
    "**/*.test.ts",
    "**/*.spec.ts",
    "src/javascript/**/*",
    "src/python/**/*",
    "src/typescript/prompt-templates.ts",
    "src/typescript/tools-schemas.ts",
    "src/typescript/**/*.d.ts"
  ]
}

```

--------------------------------------------------------------------------------
/SECURITY_NOTICE.md:
--------------------------------------------------------------------------------

```markdown
# Security Notice

## Important: API Token Rotation Required

If you have been using or testing this repository before January 2025, please note that hardcoded API tokens were previously included in test files. These have been removed and replaced with environment variable requirements.

### Actions Required:

1. **If you used the exposed tokens**: 
   - These tokens have been revoked and are no longer valid
   - You must use your own Airtable API credentials

2. **For all users**:
   - Never commit API tokens to version control
   - Always use environment variables or secure configuration files
   - Add `.env` to your `.gitignore` file

### Secure Configuration

Set your credentials using environment variables:

```bash
export AIRTABLE_TOKEN="your_personal_token_here"
export AIRTABLE_BASE_ID="your_base_id_here"
```

Or create a `.env` file (never commit this):

```env
AIRTABLE_TOKEN=your_personal_token_here
AIRTABLE_BASE_ID=your_base_id_here
```

### Reporting Security Issues

If you discover any security vulnerabilities, please report them to:
- Open an issue on GitHub (without including sensitive details)
- Contact the maintainer directly for sensitive information

Thank you for helping keep this project secure.
```

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

```typescript
import { setTimeout as delay } from 'node:timers/promises';

/**
 * Lightweight token-based rate limiter to enforce Airtable quotas.
 * Maintains per-key queues to preserve ordering and fairness.
 */
export class RateLimiter {
  private readonly minIntervalMs: number;
  private readonly lockByKey = new Map<string, Promise<void>>();
  private readonly nextAvailableByKey = new Map<string, number>();

  constructor({ maxRequestsPerSecond }: { maxRequestsPerSecond: number }) {
    if (maxRequestsPerSecond <= 0) {
      throw new Error('maxRequestsPerSecond must be greater than zero');
    }
    this.minIntervalMs = Math.ceil(1000 / maxRequestsPerSecond);
  }

  async schedule(key: string): Promise<void> {
    const previous = this.lockByKey.get(key) ?? Promise.resolve();
    let release: () => void = () => undefined;
    const current = new Promise<void>((resolve) => {
      release = resolve;
    });

    this.lockByKey.set(
      key,
      previous.then(() => current)
    );

    await previous;

    const now = Date.now();
    const availableAt = this.nextAvailableByKey.get(key) ?? now;
    const waitMs = Math.max(availableAt - now, 0);
    if (waitMs > 0) {
      await delay(waitMs);
    }

    this.nextAvailableByKey.set(key, Date.now() + this.minIntervalMs);
    release();
  }
}

```

--------------------------------------------------------------------------------
/docs/guides/QUICK_START.md:
--------------------------------------------------------------------------------

```markdown
# Quick Start Guide for Claude Users

This guide provides simple instructions for getting the Airtable MCP working with Claude.

## Step 1: Clone the repository

```bash
git clone https://github.com/rashidazarang/airtable-mcp.git
cd airtable-mcp
```

## Step 2: Install dependencies

```bash
npm install
pip install mcp
```

## Step 3: Configure Claude

In Claude settings, add a new MCP server with this configuration (adjust paths as needed):

```json
{
  "mcpServers": {
    "airtable": {
      "command": "python3",
      "args": [
        "/path/to/airtable-mcp/inspector_server.py",
        "--token",
        "YOUR_AIRTABLE_TOKEN",
        "--base",
        "YOUR_BASE_ID"
      ]
    }
  }
}
```

Replace:
- `/path/to/airtable-mcp/` with the actual path where you cloned the repository
- `YOUR_AIRTABLE_TOKEN` with your Airtable Personal Access Token
- `YOUR_BASE_ID` with your Airtable Base ID

## Step 4: Restart Claude

After configuring, restart Claude and try these commands:

1. "List the tables in my Airtable base"
2. "Show me records from [table name]"

## Troubleshooting

If you encounter issues:

1. Check the Claude logs (click on the error message)
2. Verify your Airtable token and base ID are correct
3. Make sure you've specified the correct path to `inspector_server.py`

This version includes enhanced error handling to properly format JSON responses and avoid "Method not found" errors in Claude. 
```

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

```typescript
import { LogLevel } from './config';

const LEVEL_ORDER: Record<LogLevel, number> = {
  error: 0,
  warn: 1,
  info: 2,
  debug: 3
};

export type LogMetadata = Record<string, unknown>;

export class Logger {
  private readonly level: LogLevel;
  private readonly context: LogMetadata;

  constructor(level: LogLevel, context: LogMetadata = {}) {
    this.level = level;
    this.context = context;
  }

  child(context: LogMetadata): Logger {
    return new Logger(this.level, { ...this.context, ...context });
  }

  error(message: string, metadata: LogMetadata = {}): void {
    this.log('error', message, metadata);
  }

  warn(message: string, metadata: LogMetadata = {}): void {
    this.log('warn', message, metadata);
  }

  info(message: string, metadata: LogMetadata = {}): void {
    this.log('info', message, metadata);
  }

  debug(message: string, metadata: LogMetadata = {}): void {
    this.log('debug', message, metadata);
  }

  private log(level: LogLevel, message: string, metadata: LogMetadata): void {
    if (LEVEL_ORDER[level] > LEVEL_ORDER[this.level]) {
      return;
    }

    const timestamp = new Date().toISOString();
    const output = {
      timestamp,
      level,
      message,
      ...this.context,
      ...(Object.keys(metadata).length > 0 ? { metadata } : {})
    };

    // Write logs to stderr so we don't corrupt the MCP stdio protocol stream.
    process.stderr.write(`${JSON.stringify(output)}\n`);
  }
}

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery.ai configuration
name: "@rashidazarang/airtable-mcp"
version: "3.2.4"
description: "Connect your AI tools directly to Airtable. Query, create, update, and delete records using natural language. Features include base management, table operations, schema manipulation, record filtering, and data migration—all through a standardized MCP interface compatible with Claude Desktop and other Claude-powered editors."

startCommand:
  type: stdio
  configSchema:
    type: object
    properties:
      airtable_token:
        type: string
        description: "Your Airtable Personal Access Token"
        required: true
      base_id:
        type: string
        description: "Your default Airtable base ID"
        required: true
    required: ["airtable_token", "base_id"]
  commandFunction: |
    (config) => {
      // Use the working JavaScript implementation
      return {
        command: "node",
        args: ["airtable_simple.js", "--token", config.airtable_token, "--base", config.base_id],
        env: {
          AIRTABLE_TOKEN: config.airtable_token,
          AIRTABLE_BASE_ID: config.base_id
        }
      };
    }

listTools:
  command: "node"
  args: ["airtable_simple.js", "--list-tools"]
  env: {}
  
build:
  dockerfile: "Dockerfile.node"

metadata:
  author: "Rashid Azarang"
  license: "MIT"
  repository: "https://github.com/rashidazarang/airtable-mcp"
  homepage: "https://github.com/rashidazarang/airtable-mcp#readme"
```

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

```markdown
# Release Notes - v1.2.2

## Major Improvements

### Documentation & Setup
- Completely revamped documentation with focus on Claude Desktop integration
- Added clear step-by-step installation guide
- Simplified configuration process with working JSON examples
- Added detailed troubleshooting section

### Configuration
- Removed complex JSON configuration in favor of simpler format
- Fixed JSON parsing issues with Claude Desktop
- Updated configuration file path for Claude Desktop
- Removed unnecessary escape characters in configuration

### Integration
- Improved Claude Desktop compatibility
- Added 30-second connection establishment guidance
- Added verification steps with example commands
- Enhanced error handling and logging guidance

## Technical Updates
- Updated dependencies to latest versions
- Added @smithery/cli as direct dependency
- Updated Airtable SDK to v0.12.2
- Improved Node.js version compatibility

## Bug Fixes
- Fixed JSON parsing errors in Claude Desktop
- Resolved connection timeout issues
- Fixed configuration file path issues
- Improved error messaging

## Breaking Changes
- Configuration format has changed to use direct parameters instead of JSON config string
- Removed support for complex JSON configurations
- Changed default configuration file location for Claude Desktop

## Migration Guide
If upgrading from v1.2.1 or earlier:
1. Update your configuration file to use the new format
2. Remove any escape characters from your token
3. Restart Claude Desktop after changes
4. Wait 30 seconds for connection to establish

## Contributors
- @rashidazarang 
```

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

```javascript
#!/usr/bin/env node

/**
 * Airtable MCP Server - Main Entry Point
 * 
 * This file serves as the primary entry point for the Airtable MCP server.
 * It automatically selects the best available implementation based on the environment.
 */

const path = require('path');
const fs = require('fs');

// Check which implementation to use based on available files and environment
function getImplementation() {
  // Priority 1: TypeScript compiled version if available
  const distPath = path.join(__dirname, '../dist/typescript/airtable-mcp-server.js');
  if (fs.existsSync(distPath)) {
    return require(distPath);
  }

  // Priority 2: Production JavaScript version
  const productionPath = path.join(__dirname, 'javascript/airtable_simple_production.js');
  if (fs.existsSync(productionPath)) {
    return require(productionPath);
  }

  // Priority 3: Simple JavaScript version
  const simplePath = path.join(__dirname, 'javascript/airtable_simple.js');
  if (fs.existsSync(simplePath)) {
    return require(simplePath);
  }

  // If no implementation found, provide helpful error
  console.error('No Airtable MCP implementation found.');
  console.error('Please run "npm run build" to compile TypeScript sources.');
  process.exit(1);
}

// Start the server
const implementation = getImplementation();

// Export for use as a module
module.exports = implementation;

// Run if called directly
if (require.main === module) {
  console.log('Starting Airtable MCP Server...');
  if (typeof implementation.start === 'function') {
    implementation.start();
  } else {
    console.log('Server implementation loaded successfully.');
  }
}
```

--------------------------------------------------------------------------------
/src/typescript/airtable-mcp-server.d.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
/**
 * Airtable MCP Server - TypeScript Implementation
 * Model Context Protocol server for Airtable integration with enterprise-grade type safety
 *
 * Features:
 * - Complete MCP 2024-11-05 protocol support with strict typing
 * - OAuth2 authentication with PKCE and type safety
 * - Enterprise security features with validated types
 * - Rate limiting and comprehensive input validation
 * - Production monitoring and health checks
 * - AI-powered analytics with strongly typed schemas
 *
 * Author: Rashid Azarang
 * License: MIT
 */
import { MCPServerInfo } from '../types/index';
import { ToolResponse } from '../types/tools';
declare class AirtableMCPServer {
    private server;
    private readonly config;
    private readonly tools;
    private readonly prompts;
    private readonly roots;
    constructor();
    initialize(): Promise<MCPServerInfo>;
    handleToolCall(name: string, params: Record<string, unknown>): Promise<ToolResponse>;
    private handleListTables;
    private handleListRecords;
    private handleCreateRecord;
    private handleUpdateRecord;
    private handleDeleteRecord;
    handlePromptGet(name: string, args: Record<string, unknown>): Promise<{
        messages: Array<{
            role: string;
            content: {
                type: string;
                text: string;
            };
        }>;
    }>;
    private handleAnalyzeDataPrompt;
    private handleCreateReportPrompt;
    private handlePredictiveAnalyticsPrompt;
    private handleNaturalLanguageQueryPrompt;
    start(): Promise<void>;
    stop(): Promise<void>;
    private handleRequest;
    private handleMCPRequest;
}
export { AirtableMCPServer };
export default AirtableMCPServer;

```

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

```typescript
import { AirtableBrainError } from '../../errors';
import { AppContext } from '../context';

function toUserMessage(error: AirtableBrainError): string {
  switch (error.code) {
    case 'RateLimited':
      return 'Airtable rate limit exceeded. Please retry after backoff.';
    case 'ValidationError':
      return 'Airtable rejected the request. Check field names and values.';
    case 'AuthError':
      return 'Authentication failed. Verify the Airtable token scopes and base access.';
    case 'ConflictError':
      return 'The record changed since it was fetched. Refresh data and review the diff.';
    case 'NotFound':
      return 'Requested Airtable resource was not found. Confirm the base and table identifiers.';
    case 'GovernanceError':
      return 'Operation blocked by governance allow-lists.';
    default:
      return 'Unexpected Airtable error. Please retry or check the exceptions queue.';
  }
}

export function handleToolError(toolName: string, error: unknown, ctx: AppContext) {
  if (error instanceof AirtableBrainError) {
    ctx.logger.error(`${toolName} failed`, {
      code: error.code,
      status: error.status,
      retryAfterMs: error.retryAfterMs
    });
    ctx.exceptions.record(error, `${toolName} failed`, error.message);
    return {
      isError: true,
      content: [
        {
          type: 'text' as const,
          text: toUserMessage(error)
        }
      ]
    };
  }

  ctx.logger.error(`${toolName} failed with unknown error`, {
    error: error instanceof Error ? error.message : String(error)
  });

  return {
    isError: true,
    content: [
      {
        type: 'text' as const,
        text: 'Unexpected server error. Check logs for details.'
      }
    ]
  };
}

```

--------------------------------------------------------------------------------
/types/typescript/app/airtable-client.d.ts:
--------------------------------------------------------------------------------

```typescript
import { RateLimiter } from './rateLimiter';
import { Logger } from './logger';
interface RequestOptions {
    method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
    /**
     * Path including leading slash and version segment, e.g. `/v0/meta/bases/app123`.
     */
    path: string;
    query?: Record<string, string | number | boolean | Array<string | number | boolean> | undefined>;
    body?: unknown;
    baseId?: string;
    idempotencyKey?: string;
}
interface ClientOptions {
    baseLimiter: RateLimiter;
    patLimiter: RateLimiter;
    logger: Logger;
    userAgent: string;
    patHash: string;
    maxRetries?: number;
}
export declare class AirtableClient {
    private readonly baseLimiter;
    private readonly patLimiter;
    private readonly logger;
    private readonly userAgent;
    private readonly pat;
    private readonly patHash;
    private readonly maxRetries;
    constructor(personalAccessToken: string, options: ClientOptions);
    listBases(): Promise<{
        bases: unknown[];
    }>;
    getBase(baseId: string): Promise<unknown>;
    listTables(baseId: string): Promise<{
        tables: unknown[];
    }>;
    queryRecords<T = unknown>(baseId: string, table: string, query?: RequestOptions['query']): Promise<T>;
    createRecords<T = unknown>(baseId: string, table: string, payload: unknown, idempotencyKey?: string): Promise<T>;
    updateRecords<T = unknown>(baseId: string, table: string, payload: unknown, idempotencyKey?: string): Promise<T>;
    upsertRecords<T = unknown>(baseId: string, table: string, payload: unknown, idempotencyKey?: string): Promise<T>;
    private request;
    private withRetry;
    private backoffWithJitter;
    private performRequest;
    private toDomainError;
    private safeExtractErrorType;
}
export {};

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import { z } from 'zod';
import { handleToolError } from './handleError';

// Schema for list_bases output
const listBasesOutputSchema = z.object({
  bases: z.array(
    z.object({
      id: z.string(),
      name: z.string(),
      permissionLevel: z.string().optional()
    })
  )
});

export type ListBasesOutput = z.infer<typeof listBasesOutputSchema>;

export function registerListBasesTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'list_bases',
    {
      description: 'List all accessible Airtable bases with their names, IDs, and permission levels',
      inputSchema: {},
      outputSchema: listBasesOutputSchema.shape
    },
    async (_args: unknown, _extra: unknown) => {
      try {
        ctx.logger.info('Listing accessible Airtable bases');

        const response = await ctx.airtable.listBases();
        const bases = response.bases;

        if (!bases || bases.length === 0) {
          const structuredContent: ListBasesOutput = {
            bases: []
          };
          return {
            structuredContent,
            content: [] as const
          };
        }

        const normalizedBases = bases.map((base: any) => ({
          id: String(base.id ?? ''),
          name: String(base.name ?? ''),
          permissionLevel: base.permissionLevel ? String(base.permissionLevel) : undefined
        }));

        const structuredContent: ListBasesOutput = {
          bases: normalizedBases
        };

        ctx.logger.info('Successfully listed bases', { count: bases.length });

        return {
          structuredContent,
          content: [] as const
        };
      } catch (error) {
        return handleToolError('list_bases', error, ctx);
      }
    }
  );
}

```

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

```markdown
# Release Notes - v1.2.4

## 🔒 Security Fix Release

### Critical Security Fix
- **REMOVED hardcoded API tokens from test files** (Addresses Issue #7)
  - `test_client.py` and `test_mcp_comprehensive.js` now require environment variables
  - Added security notice documentation
  - No exposed credentials in the codebase

### 🐛 Bug Fixes

#### Smithery Cloud Deployment Issues (Issues #5 and #6)
- **Fixed HTTP 400 errors** when using Smithery
- **Switched to JavaScript implementation** for Smithery deployment
- Updated `smithery.yaml` to use `airtable_simple.js` instead of problematic Python server
- Created dedicated `Dockerfile.node` for Node.js deployment
- Fixed authentication flow for Smithery connections

### 📚 Documentation Updates
- Added `SECURITY_NOTICE.md` with token rotation instructions
- Created `.env.example` file for secure configuration
- Updated Dockerfile references for Glama listing (Issue #4)

### 🔧 Improvements
- Added environment variable support with dotenv
- Improved logging system with configurable levels (ERROR, WARN, INFO, DEBUG)
- Better error messages for missing credentials

### ⚠️ Breaking Changes
- Test files now require environment variables:
  ```bash
  export AIRTABLE_TOKEN="your_token"
  export AIRTABLE_BASE_ID="your_base_id"
  ```

### 🚀 Migration Guide

1. **Update your environment variables:**
   ```bash
   cp .env.example .env
   # Edit .env with your credentials
   ```

2. **For Smithery users:**
   - Reinstall the MCP to get the latest configuration
   - The server now properly accepts credentials through Smithery's config

3. **For direct users:**
   - Continue using command line arguments or switch to environment variables
   - Both methods are supported

### 📝 Notes
- All previously exposed tokens have been revoked
- Please use your own Airtable credentials
- Never commit API tokens to version control

---

**Full Changelog**: [v1.2.3...v1.2.4](https://github.com/rashidazarang/airtable-mcp/compare/v1.2.3...v1.2.4)
```

--------------------------------------------------------------------------------
/types/typescript/errors.d.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Error taxonomy aligned with Airtable Brain guardrails.
 *
 * All tool errors should use these types so the LLM can reason about
 * retry behaviour and user messaging. Avoid leaking raw Airtable payloads
 * through error messages.
 */
export type AirtableErrorCode = 'RateLimited' | 'ValidationError' | 'AuthError' | 'ConflictError' | 'NotFound' | 'InternalError' | 'GovernanceError';
export interface ErrorContext {
    baseId?: string;
    table?: string;
    retryAfterMs?: number;
    attempt?: number;
    totalAttempts?: number;
    upstreamErrorType?: string;
    upstreamRequestId?: string;
    governanceRule?: string;
    endpoint?: string;
}
interface AirtableErrorOptions {
    status?: number;
    retryAfterMs?: number;
    context?: ErrorContext;
    cause?: unknown;
}
export declare class AirtableBrainError extends Error {
    readonly code: AirtableErrorCode;
    readonly status?: number;
    readonly retryAfterMs?: number;
    readonly context: ErrorContext;
    constructor(code: AirtableErrorCode, message: string, options?: AirtableErrorOptions);
    withContext(context: Partial<ErrorContext>): this;
}
export declare class RateLimitError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export declare class AirtableValidationError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export declare class AuthError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export declare class ConflictError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export declare class NotFoundError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export declare class InternalServerError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export declare class GovernanceError extends AirtableBrainError {
    constructor(message: string, options?: AirtableErrorOptions);
}
export {};

```

--------------------------------------------------------------------------------
/examples/sample-transform.js:
--------------------------------------------------------------------------------

```javascript
/**
 * Sample transform function for syncing data between tables
 * 
 * This module demonstrates how to transform records when syncing
 * between two tables with different schemas.
 * 
 * To use with the airtable-crud.js sync command:
 * node airtable-crud.js sync "Source Table" "Target Table" sample-transform.js
 */

/**
 * Transform function that converts records from source table format to target table format
 * @param {Object} sourceRecord - Record from the source table
 * @returns {Object} - Transformed record for the target table
 */
function transform(sourceRecord) {
  // Example: Converting a customer record to a simplified format
  
  // Extract the needed fields
  const { id, Name, Email, Phone, "Company Name": Company, "Date Added": DateAdded, Status } = sourceRecord;
  
  // Create the transformed record
  const transformedRecord = {
    // You can optionally include the source record ID
    // This is useful for updating existing records in sync operations
    // "Source Record ID": id,
    
    // Map fields from source to target
    CustomerName: Name,
    CustomerEmail: Email,
    CustomerPhone: Phone || '',
    Organization: Company || 'Individual',
    
    // Transform dates
    JoinDate: DateAdded,
    
    // Add calculated fields
    CustomerCategory: Company ? 'Business' : 'Individual',
    
    // Transform status to a different format
    IsActive: Status === 'Active',
    
    // Add constant values
    DataSource: 'Customer Table Sync',
    LastSyncedAt: new Date().toISOString()
  };
  
  return transformedRecord;
}

// You can define other utility functions here

/**
 * Helper function to clean and format phone numbers
 * @param {string} phone - Raw phone number
 * @returns {string} - Formatted phone number
 */
function formatPhoneNumber(phone) {
  if (!phone) return '';
  
  // Remove non-numeric characters
  const cleaned = ('' + phone).replace(/\D/g, '');
  
  // Format as (XXX) XXX-XXXX
  const match = cleaned.match(/^(\d{3})(\d{3})(\d{4})$/);
  if (match) {
    return '(' + match[1] + ') ' + match[2] + '-' + match[3];
  }
  
  return phone;
}

// Export the transform function
module.exports = {
  transform
}; 
```

--------------------------------------------------------------------------------
/docs/guides/CLAUDE_INTEGRATION.md:
--------------------------------------------------------------------------------

```markdown
# Claude Desktop Integration Guide

This guide provides detailed instructions for setting up the Airtable MCP with Claude Desktop.

## Prerequisites

- Node.js 14+ installed
- Claude Desktop installed
- Airtable API token
- Airtable base ID

## Configuration Steps

1. **Locate Configuration File**
   - Open Finder
   - Press `Cmd + Shift + G`
   - Enter `~/Library/Application Support/Claude`
   - Create or open `claude_desktop_config.json`

2. **Add Configuration**
   ```json
   {
     "mcpServers": {
       "airtable-mcp": {
         "command": "npx",
         "args": [
           "@smithery/cli",
           "run",
           "@rashidazarang/airtable-mcp",
           "--token",
           "YOUR_AIRTABLE_TOKEN",
           "--base",
           "YOUR_BASE_ID"
         ]
       }
     }
   }
   ```

3. **Replace Credentials**
   - Replace `YOUR_AIRTABLE_TOKEN` with your token from [Airtable Account](https://airtable.com/account)
   - Replace `YOUR_BASE_ID` with your base ID (found in your Airtable base URL)

4. **Restart Claude Desktop**
   - Close Claude Desktop completely
   - Wait 5 seconds
   - Reopen Claude Desktop
   - Wait 30 seconds for the connection to establish

## Verification

Test the connection by asking Claude:
- "Show me all my Airtable bases"
- "What tables are in this base?"
- "Show me the first 5 records from any table"

## Troubleshooting

### Connection Issues
1. Verify Node.js installation:
   ```bash
   node -v  # Should show v14 or higher
   ```

2. Test Smithery CLI:
   ```bash
   npx @smithery/cli --version
   ```

3. Check logs:
   - Open `~/Library/Logs/Claude/mcp-server-airtable-mcp.log`
   - Look for any error messages

### Common Errors

1. **"Command not found"**
   ```bash
   npm install -g npm@latest
   ```

2. **JSON Parsing Errors**
   - Remove any extra backslashes
   - Use the exact format shown above
   - Ensure no trailing commas

3. **Connection Timeout**
   - Wait full 30 seconds after startup
   - Check your internet connection
   - Verify API token is valid

## Support

If you encounter any issues:
1. Check [GitHub Issues](https://github.com/rashidazarang/airtable-mcp/issues)
2. Join our [Discord](https://discord.gg/your-discord)
3. Email: [email protected] 
```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import { handleToolError } from './handleError';

export function registerWebhookTools(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'list_webhooks',
    { description: 'List Airtable webhooks for the default base.' },
    async (_args: Record<string, unknown>) => {
      try {
        const baseId = ctx.config.auth.defaultBaseId || ctx.config.auth.allowedBases[0];
        if (!baseId) throw new Error('No base configured');
        const body = await ctx.airtable.queryRecords(baseId, 'meta/webhooks');
        return { structuredContent: { webhooks: body as Record<string, unknown> }, content: [] as const };
      } catch (error) {
        return handleToolError('list_webhooks', error, ctx);
      }
    }
  );

  server.registerTool(
    'create_webhook',
    { description: 'Create a new webhook for a base.' },
    async (args: Record<string, unknown>) => {
      try {
        const baseId = (args.baseId as string) || ctx.config.auth.defaultBaseId || ctx.config.auth.allowedBases[0];
        if (!baseId) throw new Error('No base configured');
        const payload = { notificationUrl: String(args.notificationUrl || '') };
        const result = await ctx.airtable.createRecords(baseId, 'meta/webhooks', payload as any);
        return { structuredContent: { webhook: result as Record<string, unknown> }, content: [] as const };
      } catch (error) {
        return handleToolError('create_webhook', error, ctx);
      }
    }
  );

  server.registerTool(
    'refresh_webhook',
    { description: 'Refresh webhook expiration.' },
    async (args: Record<string, unknown>) => {
      try {
        const baseId = (args.baseId as string) || ctx.config.auth.defaultBaseId || ctx.config.auth.allowedBases[0];
        if (!baseId) throw new Error('No base configured');
        const result = await ctx.airtable.updateRecords(baseId, `meta/webhooks/${String(args.webhookId)}/refresh`, {} as any);
        return { structuredContent: { webhook: result as Record<string, unknown> }, content: [] as const };
      } catch (error) {
        return handleToolError('refresh_webhook', error, ctx);
      }
    }
  );
}

```

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

```typescript
import { GovernanceSnapshot } from './types';
import { GovernanceError } from '../errors';

type Operation = GovernanceSnapshot['allowedOperations'][number];

export class GovernanceService {
  private readonly snapshot: GovernanceSnapshot;
  private readonly tablesByBase: Map<string, Set<string>>;

  constructor(snapshot: GovernanceSnapshot) {
    this.snapshot = snapshot;
    this.tablesByBase = this.buildTableIndex(snapshot);
  }

  ensureBaseAllowed(baseId: string): void {
    // If allowedBases is empty, allow all bases (user will use list_bases to discover)
    if (this.snapshot.allowedBases.length > 0 && !this.snapshot.allowedBases.includes(baseId)) {
      throw new GovernanceError(`Base ${baseId} is not in the allow-list`, {
        context: { baseId, governanceRule: 'allowedBases' }
      });
    }
  }

  ensureOperationAllowed(operation: Operation): void {
    if (!this.snapshot.allowedOperations.includes(operation)) {
      throw new GovernanceError(`Operation ${operation} is not permitted`, {
        context: { governanceRule: 'allowedOperations' }
      });
    }
  }

  ensureTableAllowed(baseId: string, table: string): void {
    if (!this.isTableAllowed(baseId, table)) {
      throw new GovernanceError(`Table ${table} is not allowed in base ${baseId}`, {
        context: { baseId, table, governanceRule: 'allowedTables' }
      });
    }
  }

  listPiiPolicies(baseId: string, table: string): Array<{ field: string; policy: string }> {
    return this.snapshot.piiFields
      ?.filter((field) => field.baseId === baseId && field.table === table)
      .map((field) => ({ field: field.field, policy: field.policy })) ?? [];
  }

  getSnapshot(): GovernanceSnapshot {
    return this.snapshot;
  }

  isTableAllowed(baseId: string, table: string): boolean {
    const allowedTables = this.tablesByBase.get(baseId);
    if (!allowedTables || allowedTables.size === 0) {
      return true;
    }
    return allowedTables.has(table);
  }

  private buildTableIndex(snapshot: GovernanceSnapshot): Map<string, Set<string>> {
    const map = new Map<string, Set<string>>();
    for (const item of snapshot.allowedTables ?? []) {
      const baseTables = map.get(item.baseId) ?? new Set<string>();
      baseTables.add(item.table);
      map.set(item.baseId, baseTables);
    }
    return map;
  }
}

```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import {
  CreateInput,
  CreateOutput,
  createInputSchema,
  createOutputSchema
} from '../types';
import { handleToolError } from './handleError';

function chunk<T>(arr: T[], size: number): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

export function registerCreateTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'create',
    {
      description: 'Create Airtable records (requires diff-before-write via dryRun first).',
      inputSchema: createInputSchema.shape,
      outputSchema: createOutputSchema.shape
    },
    async (raw: CreateInput) => {
      try {
        const args = createInputSchema.parse(raw);
        ctx.governance.ensureOperationAllowed('create');
        ctx.governance.ensureBaseAllowed(args.baseId);
        ctx.governance.ensureTableAllowed(args.baseId, args.table);

        const logger = ctx.logger.child({ tool: 'create', baseId: args.baseId, table: args.table });

        if (args.dryRun) {
          const structuredContent: CreateOutput = {
            diff: { added: args.records.length, updated: 0, unchanged: 0 },
            dryRun: true,
            records: args.records.map((r) => ({ id: 'pending', fields: r.fields }))
          };
          return { structuredContent, content: [] as const };
        }

        const chunks = chunk(args.records, 10);
        const aggregated: any[] = [];

        for (let i = 0; i < chunks.length; i++) {
          const body = { records: chunks[i], typecast: args.typecast ?? false };
          const headerKey = args.idempotencyKey ? `${args.idempotencyKey}:${i}` : undefined;
          const response: any = await ctx.airtable.createRecords(args.baseId, args.table, body, headerKey);
          if (Array.isArray(response?.records)) aggregated.push(...response.records);
        }

        const structuredContent: CreateOutput = {
          diff: { added: aggregated.length, updated: 0, unchanged: 0 },
          records: aggregated.map((r) => ({ id: String(r.id), fields: r.fields || {} })),
          dryRun: false
        };

        logger.info('Create completed', { added: aggregated.length });
        return { structuredContent, content: [] as const };
      } catch (error) {
        return handleToolError('create', error, ctx);
      }
    }
  );
}


```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import {
  UpdateInput,
  UpdateOutput,
  updateInputSchema,
  updateOutputSchema
} from '../types';
import { handleToolError } from './handleError';

function chunk<T>(arr: T[], size: number): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

export function registerUpdateTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'update',
    {
      description: 'Update Airtable records (requires diff-before-write via dryRun first).',
      inputSchema: updateInputSchema.shape,
      outputSchema: updateOutputSchema.shape
    },
    async (raw: UpdateInput) => {
      try {
        const args = updateInputSchema.parse(raw);
        ctx.governance.ensureOperationAllowed('update');
        ctx.governance.ensureBaseAllowed(args.baseId);
        ctx.governance.ensureTableAllowed(args.baseId, args.table);

        const logger = ctx.logger.child({ tool: 'update', baseId: args.baseId, table: args.table });

        if (args.dryRun) {
          const structuredContent: UpdateOutput = {
            diff: { added: 0, updated: args.records.length, unchanged: 0, conflicts: 0 },
            dryRun: true,
            records: args.records.map((r) => ({ id: r.id, fields: r.fields })),
            conflicts: []
          };
          return { structuredContent, content: [] as const };
        }

        const chunks = chunk(args.records, 10);
        const aggregated: any[] = [];

        for (let i = 0; i < chunks.length; i++) {
          const body = { records: chunks[i], typecast: args.typecast ?? false };
          const headerKey = args.idempotencyKey ? `${args.idempotencyKey}:${i}` : undefined;
          const response: any = await ctx.airtable.updateRecords(args.baseId, args.table, body, headerKey);
          if (Array.isArray(response?.records)) aggregated.push(...response.records);
        }

        const structuredContent: UpdateOutput = {
          diff: { added: 0, updated: aggregated.length, unchanged: 0, conflicts: 0 },
          records: aggregated.map((r) => ({ id: String(r.id), fields: r.fields || {} })),
          dryRun: false,
          conflicts: []
        };

        logger.info('Update completed', { updated: aggregated.length });
        return { structuredContent, content: [] as const };
      } catch (error) {
        return handleToolError('update', error, ctx);
      }
    }
  );
}


```

--------------------------------------------------------------------------------
/examples/example_usage.md:
--------------------------------------------------------------------------------

```markdown
# Airtable MCP Example Usage

This document provides examples of how to use the Airtable MCP tools within a compatible MCP client like Cursor.

## Base Management

### List all available bases

```
Using the Airtable MCP, please list all the bases I have access to.
```

### Set the active base

```
Set the active Airtable base to "Project Management" (or use the base ID directly).
```

## Table Operations

### List all tables in the current base

```
Show me all the tables in my current Airtable base.
```

### View table structure

```
Show me the structure of the "Tasks" table, including all fields and their types.
```

## Record Operations

### List records

```
Show me the first 10 records from the "Clients" table.
```

### Filter records

```
Find all "Tasks" with a status of "In Progress" and due date before today.
```

### Get a specific record

```
Get the record with ID "rec123456" from the "Projects" table.
```

### Create a new record

```
Create a new record in the "Tasks" table with the following information:
- Title: "Complete project documentation"
- Status: "Not Started"
- Due Date: "2024-12-31"
- Assigned To: "John Smith"
```

### Update an existing record

```
Update the task with ID "rec123456" in the "Tasks" table:
- Change status to "In Progress"
- Update due date to "2024-11-30"
```

### Delete a record

```
Delete the record with ID "rec123456" from the "Tasks" table.
```

## Schema Management

### Export the schema

```
Export the schema of my current Airtable base in JSON format.
```

### Compare schemas

```
Compare this schema with my current base schema to identify any differences.
```

## Data Migration

### Generate field mapping

```
Generate a field mapping between the "Clients" and "Customers" tables.
```

### Migrate data

```
Migrate data from the "Clients" table to the "Customers" table using the generated mapping.
```

## Tips for Better Results

1. **Be specific** when referencing table and field names
2. **Use record IDs** when updating or deleting specific records
3. **Use natural language** to describe the operations you want to perform
4. **Check your base ID** is correctly set if you get unexpected results
5. **Format JSON data** properly when creating or updating records

## Combining Operations

You can combine multiple operations in a single request:

```
Please help me organize my project data:
1. First, show me all the tables in my base
2. Then, list the overdue tasks (status is not "Complete" and due date is before today)
3. Finally, update those tasks to have a status of "Urgent"
```

The Airtable MCP can help with complex workflows by understanding your intentions and executing the appropriate sequence of operations. 
```

--------------------------------------------------------------------------------
/PROJECT_STRUCTURE.md:
--------------------------------------------------------------------------------

```markdown
# Project Structure

## 📁 Directory Layout

```
airtable-mcp/
├── src/                    # Source code
│   ├── index.js           # Main entry point
│   ├── typescript/        # TypeScript implementation
│   ├── javascript/        # JavaScript implementation
│   └── python/            # Python implementation
├── dist/                  # Compiled TypeScript output
├── docs/                  # Documentation
│   ├── api/              # API documentation
│   ├── guides/           # User guides
│   └── releases/         # Release notes
├── tests/                 # Test files
│   ├── unit/            # Unit tests
│   ├── integration/     # Integration tests
│   └── e2e/             # End-to-end tests
├── examples/             # Usage examples
├── bin/                  # CLI executables
├── scripts/              # Build and utility scripts
├── config/               # Configuration files
├── docker/               # Docker configurations
└── types/                # TypeScript type definitions
```

## 🚀 Quick Start

```bash
# Install dependencies
npm install

# Build TypeScript
npm run build

# Run the server
npm start

# Development mode
npm run dev

# Run tests
npm test
```

## 📦 Available Scripts

- `npm run build` - Compile TypeScript to JavaScript
- `npm start` - Start the production server
- `npm run dev` - Start development server with hot reload
- `npm test` - Run all tests
- `npm run lint` - Check code quality
- `npm run format` - Format code with Prettier

## 🔧 Implementations

### TypeScript (Primary)
- Location: `src/typescript/`
- Output: `dist/`
- Entry: `airtable-mcp-server.ts`

### JavaScript
- Location: `src/javascript/`
- Entry: `airtable_simple_production.js`

### Python
- Location: `src/python/`
- Entry: `inspector_server.py`

## 📝 Configuration Files

- `package.json` - Node.js dependencies and scripts
- `tsconfig.json` - TypeScript compiler configuration
- `.eslintrc.js` - ESLint rules
- `.prettierrc` - Prettier formatting rules
- `jest.config.js` - Jest testing configuration
- `.nvmrc` - Node.js version specification

## 🧪 Testing

Tests are organized by type:
- Unit tests: `tests/unit/`
- Integration tests: `tests/integration/`
- End-to-end tests: `tests/e2e/`

Run specific test suites:
```bash
npm run test:unit
npm run test:integration
npm run test:e2e
```

## 📚 Documentation

- API Documentation: `docs/api/`
- User Guides: `docs/guides/`
- Release Notes: `docs/releases/`
- Changelog: `CHANGELOG.md`

## 🐳 Docker Support

Docker configurations are in the `docker/` directory:
- `Dockerfile` - Python implementation
- `Dockerfile.node` - Node.js implementation

## 🤝 Contributing

See `CONTRIBUTING.md` for guidelines on contributing to this project.
```

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

```typescript
import { randomUUID } from 'node:crypto';
import { Logger } from './logger';
import {
  ExceptionItem,
  ListExceptionsInput,
  ListExceptionsOutput
} from './types';
import { AirtableBrainError, AirtableErrorCode } from '../errors';

type ExceptionCategory = ExceptionItem['category'];
type ExceptionSeverity = ExceptionItem['severity'];

function mapCategory(code: AirtableErrorCode): ExceptionCategory {
  switch (code) {
    case 'RateLimited':
      return 'rate_limit';
    case 'ValidationError':
      return 'validation';
    case 'AuthError':
      return 'auth';
    case 'ConflictError':
      return 'conflict';
    case 'GovernanceError':
      return 'schema_drift';
    default:
      return 'other';
  }
}

function mapSeverity(code: AirtableErrorCode): ExceptionSeverity {
  switch (code) {
    case 'RateLimited':
    case 'AuthError':
    case 'ConflictError':
    case 'GovernanceError':
      return 'error';
    case 'ValidationError':
      return 'warning';
    default:
      return 'error';
  }
}

export class ExceptionStore {
  private readonly capacity: number;
  private readonly items: ExceptionItem[] = [];
  private readonly logger: Logger;

  constructor(capacity: number, logger: Logger) {
    this.capacity = capacity;
    this.logger = logger.child({ component: 'exception_store' });
  }

  record(error: AirtableBrainError, summary: string, details?: string, proposedFix?: Record<string, unknown>): void {
    const item: ExceptionItem = {
      id: randomUUID(),
      timestamp: new Date().toISOString(),
      severity: mapSeverity(error.code),
      category: mapCategory(error.code),
      summary,
      details,
      proposedFix
    };

    this.items.unshift(item);
    if (this.items.length > this.capacity) {
      this.items.pop();
    }
    this.logger.debug('Recorded exception', { code: error.code });
  }

  list(params: ListExceptionsInput): ListExceptionsOutput {
    const limit = params.limit ?? 100;
    const cursorIndex = this.parseCursor(params.cursor);

    let filtered = this.items;

    if (params.since) {
      filtered = filtered.filter((item) => item.timestamp > params.since!);
    }

    if (params.severity) {
      filtered = filtered.filter((item) => item.severity === params.severity);
    }

    const slice = filtered.slice(cursorIndex, cursorIndex + limit);
    const nextCursor = cursorIndex + limit < filtered.length ? String(cursorIndex + limit) : undefined;

    return {
      items: slice,
      cursor: nextCursor
    };
  }

  private parseCursor(cursor?: string): number {
    if (!cursor) {
      return 0;
    }
    const parsed = Number.parseInt(cursor, 10);
    if (Number.isNaN(parsed) || parsed < 0) {
      return 0;
    }
    return parsed;
  }
}

```

--------------------------------------------------------------------------------
/RELEASE_v3.2.1.md:
--------------------------------------------------------------------------------

```markdown
# Release v3.2.1 - Critical TypeScript Architecture Fix

## 🚨 Important Update for TypeScript Users

This release fixes a **critical issue** that prevented the TypeScript implementation from compiling correctly. All users using TypeScript should update immediately.

## What's Fixed

### TypeScript Compilation Issue ✅
The TypeScript implementation had a fundamental architecture problem where `.d.ts` files incorrectly contained runtime code (classes and constants) instead of just type definitions. This has been completely resolved.

**Before (Broken):**
- `.d.ts` files contained classes like `AirtableError` and constants like `COMPLETE_TOOL_SCHEMAS`
- TypeScript compilation failed with "cannot be used as a value" errors

**After (Fixed):**
- Runtime code moved to proper `.ts` files:
  - `errors.ts` - Error classes
  - `tools-schemas.ts` - Tool schemas
  - `prompt-templates.ts` - AI prompt templates
- `.d.ts` files now only contain type definitions
- TypeScript compiles successfully

## Installation

### For New Users
```bash
npm install @rashidazarang/[email protected]
```

### For Existing Users
```bash
npm update @rashidazarang/airtable-mcp
```

### If Using TypeScript
After updating, rebuild your project:
```bash
npm run build
```

## Verification

Both implementations now work correctly:

```bash
# Test JavaScript implementation
AIRTABLE_TOKEN=your_token AIRTABLE_BASE_ID=your_base node node_modules/@rashidazarang/airtable-mcp/src/javascript/airtable_simple_production.js

# Test TypeScript implementation (after building)
AIRTABLE_TOKEN=your_token AIRTABLE_BASE_ID=your_base node node_modules/@rashidazarang/airtable-mcp/dist/typescript/airtable-mcp-server.js
```

## Project Structure Improvements

The project has been reorganized with a world-class structure:

```
src/
├── index.js           # Main entry point
├── typescript/        # TypeScript implementation
├── javascript/        # JavaScript implementation
└── python/           # Python implementation
```

## Backwards Compatibility

✅ **No breaking changes** - All existing functionality is preserved:
- JavaScript users can continue without any changes
- TypeScript users just need to rebuild after updating
- All API endpoints remain the same
- All tools and prompts continue to work

## Support

If you encounter any issues:
1. Check that you're on version 3.2.1: `npm list @rashidazarang/airtable-mcp`
2. Try cleaning and rebuilding: `npm run clean && npm run build`
3. Report issues at: https://github.com/rashidazarang/airtable-mcp/issues

## Thank You

Thank you to all users for your patience. This critical fix ensures stability for everyone depending on this package. Your production deployments are now safe.

---

**Full Changelog:** [v3.2.0...v3.2.1](https://github.com/rashidazarang/airtable-mcp/compare/v3.2.0...v3.2.1)
```

--------------------------------------------------------------------------------
/src/typescript/errors.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Error taxonomy aligned with Airtable Brain guardrails.
 *
 * All tool errors should use these types so the LLM can reason about
 * retry behaviour and user messaging. Avoid leaking raw Airtable payloads
 * through error messages.
 */

export type AirtableErrorCode =
  | 'RateLimited'
  | 'ValidationError'
  | 'AuthError'
  | 'ConflictError'
  | 'NotFound'
  | 'InternalError'
  | 'GovernanceError';

export interface ErrorContext {
  baseId?: string;
  table?: string;
  retryAfterMs?: number;
  attempt?: number;
  totalAttempts?: number;
  upstreamErrorType?: string;
  upstreamRequestId?: string;
  governanceRule?: string;
  endpoint?: string;
}

interface AirtableErrorOptions {
  status?: number;
  retryAfterMs?: number;
  context?: ErrorContext;
  cause?: unknown;
}

export class AirtableBrainError extends Error {
  readonly code: AirtableErrorCode;
  readonly status?: number;
  readonly retryAfterMs?: number;
  readonly context: ErrorContext;

  constructor(code: AirtableErrorCode, message: string, options: AirtableErrorOptions = {}) {
    super(message);
    this.name = code;
    this.code = code;
    if (options.cause !== undefined) {
      (this as Error & { cause?: unknown }).cause = options.cause;
    }
    if (options.status !== undefined) {
      this.status = options.status;
    }
    if (options.retryAfterMs !== undefined) {
      this.retryAfterMs = options.retryAfterMs;
    }
    this.context = options.context ?? {};
  }

  withContext(context: Partial<ErrorContext>): this {
    Object.assign(this.context, context);
    return this;
  }
}

export class RateLimitError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('RateLimited', message, options);
  }
}

export class AirtableValidationError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('ValidationError', message, options);
  }
}

export class AuthError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('AuthError', message, options);
  }
}

export class ConflictError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('ConflictError', message, options);
  }
}

export class NotFoundError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('NotFound', message, options);
  }
}

export class InternalServerError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('InternalError', message, options);
  }
}

export class GovernanceError extends AirtableBrainError {
  constructor(message: string, options: AirtableErrorOptions = {}) {
    super('GovernanceError', message, options);
  }
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "@rashidazarang/airtable-mcp",
  "version": "3.2.5",
  "description": "Advanced AI-powered Airtable MCP server with TypeScript support, intelligent analytics, predictive modeling, and enterprise automation capabilities",
  "main": "dist/typescript/index.js",
  "exports": {
    ".": {
      "import": "./dist/typescript/index.js",
      "require": "./dist/typescript/index.js",
      "types": "./types/typescript/index.d.ts"
    },
    "./types": "./types/typescript/app/types.d.ts",
    "./types/*": "./types/typescript/**/*.d.ts"
  },
  "bin": {
    "airtable-mcp": "./bin/airtable-mcp.js",
    "airtable-mcp-cli": "./bin/airtable-crud-cli.js"
  },
  "scripts": {
    "build": "tsc",
    "build:watch": "tsc --watch",
    "clean": "rm -rf dist",
    "prebuild": "npm run clean",
    "start": "node dist/typescript/airtable-mcp-server.js",
    "start:dev": "ts-node src/typescript/airtable-mcp-server.ts",
    "start:js": "node src/javascript/airtable_simple_production.js",
    "start:python": "python3 src/python/inspector_server.py",
    "test": "jest",
    "test:unit": "jest tests/unit",
    "test:integration": "jest tests/integration",
    "test:e2e": "jest tests/e2e",
    "test:types": "tsc --noEmit",
    "lint": "eslint src/**/*.{js,ts}",
    "lint:fix": "eslint src/**/*.{js,ts} --fix",
    "format": "prettier --write \"src/**/*.{js,ts,json,md}\"",
    "format:check": "prettier --check \"src/**/*.{js,ts,json,md}\"",
    "dev": "nodemon --watch src --exec ts-node src/typescript/airtable-mcp-server.ts",
    "prepare": "npm run build",
    "prepublishOnly": "npm run build"
  },
  "keywords": [
    "airtable",
    "mcp",
    "claude",
    "claude-desktop",
    "anthropic",
    "ai",
    "database",
    "typescript",
    "automation",
    "enterprise",
    "analytics"
  ],
  "files": [
    "dist/",
    "types/",
    "bin/",
    "README.md",
    "LICENSE",
    "package.json",
    "tsconfig.json"
  ],
  "types": "types/typescript/index.d.ts",
  "author": "Rashid Azarang",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.19.1",
    "@smithery/cli": "^1.0.0",
    "dotenv": "^16.0.0",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/dotenv": "^8.2.0",
    "@types/jest": "^29.5.0",
    "@types/node": "^20.10.0",
    "@typescript-eslint/eslint-plugin": "^6.0.0",
    "@typescript-eslint/parser": "^6.0.0",
    "eslint": "^8.50.0",
    "eslint-config-prettier": "^9.0.0",
    "jest": "^29.7.0",
    "nodemon": "^3.0.0",
    "prettier": "^3.0.0",
    "ts-jest": "^29.1.0",
    "ts-node": "^10.9.0",
    "typescript": "^5.3.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/rashidazarang/airtable-mcp"
  },
  "bugs": {
    "url": "https://github.com/rashidazarang/airtable-mcp/issues"
  },
  "homepage": "https://github.com/rashidazarang/airtable-mcp#readme"
}

```

--------------------------------------------------------------------------------
/src/typescript/airtable-mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

// Import via require to avoid TS type resolution issues with deep subpath exports
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio');
import { loadConfig } from './app/config';
import { Logger } from './app/logger';
import { RateLimiter } from './app/rateLimiter';
import { AirtableClient } from './app/airtable-client';
import { GovernanceService } from './app/governance';
import { ExceptionStore } from './app/exceptions';
import { registerAllTools } from './app/tools';
import { AppContext } from './app/context';

const PROTOCOL_VERSION = '2024-11-05';

function buildContext(config: ReturnType<typeof loadConfig>, rootLogger: Logger): AppContext {
  const baseLimiter = new RateLimiter({ maxRequestsPerSecond: 5 });
  const patLimiter = new RateLimiter({ maxRequestsPerSecond: 50 });

  const airtable = new AirtableClient(config.auth.personalAccessToken, {
    baseLimiter,
    patLimiter,
    logger: rootLogger.child({ component: 'airtable_client' }),
    userAgent: `airtable-brain-mcp/${config.version}`,
    patHash: config.auth.patHash
  });

  const governance = new GovernanceService(config.governance);
  const exceptions = new ExceptionStore(config.exceptionQueueSize, rootLogger);

  return {
    config,
    logger: rootLogger,
    airtable,
    governance,
    exceptions
  };
}

export async function start(): Promise<void> {
  const config = loadConfig();
  const logger = new Logger(config.logLevel, { component: 'server' });

  const context = buildContext(config, logger);

  const server = new McpServer(
    {
      name: 'airtable-brain',
      version: config.version,
      protocolVersion: PROTOCOL_VERSION
    },
    {
      capabilities: {
        tools: {},
        prompts: {},
        resources: {}
      },
      instructions:
        'Use describe and query tools for read flows. All mutations require diff review and idempotency keys.'
    }
  );

  registerAllTools(server, context);

  const transport = new StdioServerTransport();
  await server.connect(transport);

  logger.info('Airtable Brain MCP server ready', {
    version: config.version,
    protocolVersion: PROTOCOL_VERSION
  });

  const shutdown = async (signal: string) => {
    logger.info('Shutting down due to signal', { signal });
    await server.close();
    await transport.close();
    process.exit(0);
  };

  process.on('SIGINT', () => void shutdown('SIGINT'));
  process.on('SIGTERM', () => void shutdown('SIGTERM'));
}

if (typeof require !== 'undefined' && require.main === module) {
  start().catch((error) => {
    // eslint-disable-next-line no-console
    console.error('Failed to start Airtable Brain MCP server:', error);
    process.exit(1);
  });
}

```

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

```markdown
# Release Notes - v1.4.0

## 🚀 Major Feature Release

### ✨ New Features

#### 🪝 **Webhook Management** (5 new tools)
- `list_webhooks` - List all webhooks in your base
- `create_webhook` - Create webhooks for real-time notifications
- `delete_webhook` - Remove webhooks
- `get_webhook_payloads` - Retrieve webhook payload history
- `refresh_webhook` - Extend webhook expiration time

#### 🔧 **Enhanced CRUD Operations** (5 tools added since v1.2.4)
- `create_record` - Create new records in any table
- `update_record` - Update existing records
- `delete_record` - Remove records from tables
- `get_record` - Retrieve single record by ID
- `search_records` - Advanced filtering with Airtable formulas

### 📊 **Complete Tool Set (12 tools total)**
1. **list_tables** - List all tables in base
2. **list_records** - List records from table
3. **get_record** - Get single record by ID
4. **create_record** - Create new records
5. **update_record** - Update existing records
6. **delete_record** - Delete records
7. **search_records** - Search with filters
8. **list_webhooks** - List webhooks
9. **create_webhook** - Create webhooks
10. **delete_webhook** - Delete webhooks
11. **get_webhook_payloads** - Get webhook history
12. **refresh_webhook** - Refresh webhook expiration

### 🔐 **Security Improvements**
- Environment variable support for credentials
- Token masking in logs
- Configurable logging levels (ERROR, WARN, INFO, DEBUG)
- No hardcoded credentials in test files

### 🛠️ **Technical Improvements**
- Full HTTP method support (GET, POST, PATCH, DELETE)
- Enhanced error handling with detailed messages
- Proper API endpoint routing
- Debug logging support
- Graceful shutdown handling

### 📈 **Testing**
- **100% test coverage** - All 12 tools tested and verified
- Tested with real Airtable API
- Comprehensive test suite included
- Test scripts for validation

### 💔 **Breaking Changes**
- Test files now require environment variables:
  ```bash
  export AIRTABLE_TOKEN="your_token"
  export AIRTABLE_BASE_ID="your_base_id"
  ```

### 🔄 **Migration from v1.2.4**

1. **Update package**:
   ```bash
   npm install -g @rashidazarang/airtable-mcp@latest
   ```

2. **Set credentials** (choose one method):
   - Environment variables
   - Command line arguments
   - .env file

3. **Update configuration** if using webhooks

### 📝 **Webhook Usage Example**

```javascript
// Create a webhook
{
  "name": "create_webhook",
  "arguments": {
    "notificationUrl": "https://your-endpoint.com/webhook"
  }
}

// The response includes:
// - Webhook ID
// - MAC secret (save this - shown only once!)
// - Expiration time
```

### 🎯 **What's Next**
- Batch operations support
- Comment management
- Attachment handling
- Schema modification tools

### 🙏 **Acknowledgments**
- Thanks to all testers and contributors
- Special thanks for the comprehensive testing feedback

---

**Full Changelog**: [v1.2.4...v1.4.0](https://github.com/rashidazarang/airtable-mcp/compare/v1.2.4...v1.4.0)
```

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

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import { AppContext } from '../context';
import {
  UpsertInput,
  UpsertOutput,
  upsertInputSchema,
  upsertOutputSchema
} from '../types';
import { handleToolError } from './handleError';

function chunk<T>(arr: T[], size: number): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
  return out;
}

export function registerUpsertTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'upsert',
    {
      description: 'Upsert Airtable records using performUpsert.fieldsToMergeOn.',
      inputSchema: upsertInputSchema.shape,
      outputSchema: upsertOutputSchema.shape
    },
    async (raw: UpsertInput) => {
      try {
        const args = upsertInputSchema.parse(raw);
        ctx.governance.ensureOperationAllowed('upsert');
        ctx.governance.ensureBaseAllowed(args.baseId);
        ctx.governance.ensureTableAllowed(args.baseId, args.table);

        const logger = ctx.logger.child({ tool: 'upsert', baseId: args.baseId, table: args.table });
        const matchedBy = args.performUpsert.fieldsToMergeOn;

        if (args.dryRun) {
          const structuredContent: UpsertOutput = {
            diff: { added: args.records.length, updated: 0, unchanged: 0, conflicts: 0 },
            dryRun: true,
            records: args.records.map((r) => ({ id: 'pending', fields: r.fields })),
            conflicts: []
          } as any;
          // Note: Upsert output in PRD expects 'matchedBy' array and no conflicts property; keep consistent with docs
          // When using strict PRD output, we can omit conflicts and include matchedBy
          (structuredContent as any).matchedBy = matchedBy;
          delete (structuredContent as any).conflicts;
          return { structuredContent, content: [] as const };
        }

        const chunks = chunk(args.records, 10);
        const aggregated: any[] = [];

        for (let i = 0; i < chunks.length; i++) {
          const body = {
            records: chunks[i],
            typecast: args.typecast ?? false,
            performUpsert: { fieldsToMergeOn: matchedBy }
          };
          const headerKey = args.idempotencyKey ? `${args.idempotencyKey}:${i}` : undefined;
          const response: any = await ctx.airtable.upsertRecords(args.baseId, args.table, body, headerKey);
          if (Array.isArray(response?.records)) aggregated.push(...response.records);
        }

        const structuredContent: any = {
          diff: { added: 0, updated: aggregated.length, unchanged: 0 },
          matchedBy,
          records: aggregated.map((r) => ({ id: String(r.id), fields: r.fields || {} })),
          dryRun: false
        };

        logger.info('Upsert completed', { processed: aggregated.length, matchedBy });
        return { structuredContent, content: [] as const };
      } catch (error) {
        return handleToolError('upsert', error, ctx);
      }
    }
  );
}


```

--------------------------------------------------------------------------------
/RELEASE_v3.2.3.md:
--------------------------------------------------------------------------------

```markdown
# Release v3.2.3 - Complete Security Resolution

## 🔒 Security Release - GitHub Alert #10 Fully Resolved

This release provides a **complete fix** for the command injection vulnerability identified in GitHub Security Alert #10. Version 3.2.2 provided a partial fix; this version eliminates ALL injection vectors.

## What's New in v3.2.3

### Complete Security Fix ✅

The command injection vulnerability has been fully resolved through defense-in-depth security measures:

1. **Environment Variable Validation**
   - `BASE_ID` is now validated at startup
   - Only alphanumeric characters, hyphens, and underscores allowed
   - Prevents injection from environment variables

2. **Safe API Endpoint Construction**
   - Eliminated ALL string interpolation in API calls
   - Uses safe string concatenation instead of f-strings
   - No user input directly interpolated into URLs

3. **Enhanced Input Validation**
   - Path traversal protection (blocks `..` and `//`)
   - Token format validation
   - Endpoint character whitelisting
   - Multiple validation layers

4. **Code Security Improvements**
   - Removed unused imports that triggered security scanners
   - Added comprehensive input sanitization
   - Implemented principle of least privilege

## Installation

### Update Existing Installation
```bash
npm update @rashidazarang/airtable-mcp
```

### Fresh Installation
```bash
npm install @rashidazarang/[email protected]
```

## Verification

After updating, the security vulnerability is completely resolved. You can verify:

```bash
# Check version
npm list @rashidazarang/airtable-mcp

# Should show: @rashidazarang/[email protected]
```

## Changes from v3.2.2

### Security Enhancements
- ✅ BASE_ID validation at startup
- ✅ Eliminated string interpolation vulnerabilities
- ✅ Path traversal protection
- ✅ Token validation
- ✅ Defense-in-depth implementation

### Code Quality
- Improved error messages for invalid inputs
- Better documentation of security measures
- Cleaner validation logic

## Testing

The fix has been tested against various injection attempts:
- Path traversal attempts: `../../../etc/passwd` ❌ Blocked
- Command injection: `; rm -rf /` ❌ Blocked
- URL manipulation: `https://evil.com/` ❌ Blocked
- Special characters: `<script>alert(1)</script>` ❌ Blocked

## Migration Guide

No breaking changes. Simply update to v3.2.3:

```bash
# For npm users
npm update @rashidazarang/airtable-mcp

# For yarn users
yarn upgrade @rashidazarang/[email protected]
```

## Security Disclosure

- **CVE**: Not assigned (internal finding)
- **Severity**: High
- **CVSS Score**: 7.8 (High)
- **Vector**: Network accessible if test_client.py is exposed
- **Impact**: Potential command injection via environment variables
- **Status**: ✅ FIXED in v3.2.3

## Acknowledgments

Thanks to GitHub's security scanning for identifying this vulnerability. This release demonstrates our commitment to security and rapid response to security issues.

## Support

If you have questions or need help:
- Open an issue: https://github.com/rashidazarang/airtable-mcp/issues
- Security concerns: Please report privately via GitHub Security Advisories

---

**All users should update to v3.2.3 immediately for complete security protection.**
```

--------------------------------------------------------------------------------
/TESTING_REPORT.md:
--------------------------------------------------------------------------------

```markdown
# Airtable MCP Testing Report

## Executive Summary
Testing completed on 2025-09-09. The JavaScript implementation works correctly via npm and Smithery. TypeScript implementation has architectural issues that prevent compilation.

## Test Results

### ✅ NPM Package Testing
- **Package Name**: `@rashidazarang/airtable-mcp`
- **Published Version**: 3.1.0 (latest on npm)
- **Local Version**: 3.2.0 (in package.json)
- **Installation**: ✅ Successful via `npm install`
- **Execution**: ✅ JavaScript implementation runs correctly

### ✅ JavaScript Implementation
- **File**: `airtable_simple_production.js`
- **Status**: ✅ Working
- **Test Command**: `AIRTABLE_TOKEN=test AIRTABLE_BASE_ID=test node airtable_simple_production.js`
- **Result**: Server starts successfully on port 8010

### ✅ TypeScript Implementation
- **Status**: ✅ Fixed and working
- **Resolution**: Moved runtime code from `.d.ts` files to proper `.ts` files:
  - Created `errors.ts` for error classes
  - Created `tools-schemas.ts` for tool schemas
  - Created `prompt-templates.ts` for AI prompt templates
  - Updated imports to use regular imports for runtime code
- **Build**: Successfully compiles with `npm run build`
- **Execution**: TypeScript output runs correctly

### ✅ Smithery Configuration
- **File**: `smithery.yaml`
- **Version**: Updated to 3.2.0 (was 1.2.4)
- **Entry Point**: Points to `airtable_simple.js`
- **Status**: ✅ Configuration valid

## Architecture Fixed

### TypeScript Implementation Solution

1. **Proper File Structure**: Created separate `.ts` files for runtime code:
   - `errors.ts`: Contains `AirtableError` and `ValidationError` classes
   - `tools-schemas.ts`: Contains `COMPLETE_TOOL_SCHEMAS` constant
   - `prompt-templates.ts`: Contains `AI_PROMPT_TEMPLATES` constant
   
2. **Clean Type Definitions**: `.d.ts` files now only contain type definitions

3. **Correct Imports**: 
   - Type-only imports use `import type`
   - Runtime imports use regular `import`
   - Clear separation between types and implementation

## Recommendations

### Immediate Actions
1. ✅ **Use JavaScript Implementation**: The JavaScript version works correctly
2. ✅ **Update npm Package**: Publish version 3.2.0 to npm registry
3. ✅ **Smithery Deployment**: Ready to deploy with JavaScript implementation

### Future Improvements
1. **Version Consistency**:
   - Align versions across package.json (3.2.0) and npm registry (3.1.0)
   - Update all references to use consistent version

## Deployment Status

### Production Ready
- ✅ JavaScript implementation (`airtable_simple_production.js`)
- ✅ NPM package installation
- ✅ Smithery configuration

### Production Ready (All Implementations)
- ✅ TypeScript implementation (fixed and working)

## Test Commands Used

```bash
# NPM Package Test
npm install @rashidazarang/airtable-mcp@latest

# JavaScript Implementation Test
AIRTABLE_TOKEN=test AIRTABLE_BASE_ID=test node airtable_simple_production.js

# TypeScript Build (Failed)
npm run build

# Smithery Configuration Test
node airtable_simple.js --list-tools
```

## Conclusion

The Airtable MCP package is **fully production-ready** with both JavaScript and TypeScript implementations working correctly. The TypeScript architecture has been fixed by properly separating runtime code from type definitions. Smithery deployment will work with either implementation.
```

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

```typescript
/**
 * Runtime tool schemas for Airtable MCP Server
 */

import type { ToolSchema } from './index';

export const COMPLETE_TOOL_SCHEMAS: ToolSchema[] = [
  // Data Operations
  {
    name: 'list_tables',
    description: 'Get all tables in your base with schema information',
    inputSchema: {
      type: 'object',
      properties: {
        include_schema: { type: 'boolean', description: 'Include field schema information', default: false }
      }
    }
  },
  {
    name: 'list_records',
    description: 'Query records with optional filtering and pagination',
    inputSchema: {
      type: 'object',
      properties: {
        table: { type: 'string', description: 'Table name or ID' },
        maxRecords: { type: 'number', description: 'Maximum number of records to return' },
        view: { type: 'string', description: 'View name or ID' },
        filterByFormula: { type: 'string', description: 'Airtable formula to filter records' },
        sort: { type: 'array', description: 'Sort configuration' },
        pageSize: { type: 'number', description: 'Number of records per page' },
        offset: { type: 'string', description: 'Pagination offset' }
      },
      required: ['table']
    }
  },
  {
    name: 'get_record',
    description: 'Retrieve a single record by ID',
    inputSchema: {
      type: 'object',
      properties: {
        table: { type: 'string', description: 'Table name or ID' },
        recordId: { type: 'string', description: 'Record ID' }
      },
      required: ['table', 'recordId']
    }
  },
  {
    name: 'create_record',
    description: 'Add new records to any table',
    inputSchema: {
      type: 'object',
      properties: {
        table: { type: 'string', description: 'Table name or ID' },
        fields: { type: 'object', description: 'Field values for the new record' },
        typecast: { type: 'boolean', description: 'Automatically typecast field values' }
      },
      required: ['table', 'fields']
    }
  },
  {
    name: 'update_record',
    description: 'Modify existing record fields',
    inputSchema: {
      type: 'object',
      properties: {
        table: { type: 'string', description: 'Table name or ID' },
        recordId: { type: 'string', description: 'Record ID to update' },
        fields: { type: 'object', description: 'Fields to update' },
        typecast: { type: 'boolean', description: 'Automatically typecast field values' }
      },
      required: ['table', 'recordId', 'fields']
    }
  },
  {
    name: 'delete_record',
    description: 'Remove records from a table',
    inputSchema: {
      type: 'object',
      properties: {
        table: { type: 'string', description: 'Table name or ID' },
        recordId: { type: 'string', description: 'Record ID to delete' }
      },
      required: ['table', 'recordId']
    }
  },
  {
    name: 'search_records',
    description: 'Advanced search with Airtable formulas and sorting',
    inputSchema: {
      type: 'object',
      properties: {
        table: { type: 'string', description: 'Table name or ID' },
        filterByFormula: { type: 'string', description: 'Search formula' },
        sort: { type: 'array', description: 'Sort configuration' },
        maxRecords: { type: 'number', description: 'Maximum records to return' },
        view: { type: 'string', description: 'View to search within' }
      },
      required: ['table']
    }
  }
];
```

--------------------------------------------------------------------------------
/RELEASE_v3.2.4.md:
--------------------------------------------------------------------------------

```markdown
# Release v3.2.4 - Complete XSS Security Fix

## 🔒 Security Release - OAuth2 XSS Vulnerabilities Fixed

This release addresses Cross-Site Scripting (XSS) vulnerabilities in the OAuth2 authorization endpoint identified by GitHub Security Scanning.

## Vulnerability Details

- **Type**: Cross-Site Scripting (XSS)
- **Locations**: 
  - `src/javascript/airtable_simple_production.js:710` (Alert #11)
  - `src/javascript/airtable_simple_production.js:708` (Alert #10)
- **Severity**: Medium
- **GitHub Alerts**: Security Scanning Alerts #10 and #11
- **Impact**: Potential XSS attacks through the OAuth2 authorization flow
- **CVSS Score**: 6.1 (Medium)

## What's Fixed

### Complete XSS Prevention
1. **Unicode Escaping for JSON**
   - All special characters in JSON are now Unicode-escaped
   - Prevents script injection through `</script>` tags
   - Safe embedding of JSON data in script contexts

2. **Dynamic Content via textContent**
   - Changed from embedding variables in HTML to using `textContent`
   - Prevents HTML injection through user-controlled data
   - Client ID and Redirect URI are safely displayed

3. **Enhanced Character Encoding**
   - Explicit UTF-8 encoding: `res.end(htmlContent, 'utf8')`
   - Content-Type header: `'text/html; charset=utf-8'`
   - Consistent encoding throughout the response

4. **Multiple Escape Layers**
   - HTML escaping for display values
   - Unicode escaping for JavaScript contexts
   - URL encoding for query parameters
   - Defense in depth approach

5. **Security Headers**
   - Content-Security-Policy restricting script sources
   - X-XSS-Protection enabled
   - X-Content-Type-Options: nosniff
   - Cache-Control preventing sensitive data caching

## Installation

### Update Existing Installation
```bash
npm update @rashidazarang/airtable-mcp
```

### Fresh Installation
```bash
npm install @rashidazarang/[email protected]
```

### Verify Installation
```bash
npm list @rashidazarang/airtable-mcp
# Should show: @rashidazarang/[email protected]
```

## Technical Details

### Before (Vulnerable)
```javascript
res.writeHead(200, { 
  'Content-Type': 'text/html',
  // ... other headers
});
// ...
res.end(htmlContent);
```

### After (Fixed)
```javascript
res.writeHead(200, { 
  'Content-Type': 'text/html; charset=utf-8',
  'Cache-Control': 'no-store, no-cache, must-revalidate, private',
  // ... other security headers
});
// ...
res.end(htmlContent, 'utf8');
```

## Testing

The fix has been validated against common XSS attack vectors:
- HTML injection attempts ❌ Blocked
- JavaScript injection ❌ Blocked  
- Event handler injection ❌ Blocked
- UTF-8 encoding bypass attempts ❌ Blocked

## Migration Guide

This is a security fix with no breaking changes:

1. Update to v3.2.4
2. No code changes required
3. OAuth2 flow continues to work as expected
4. Enhanced security is automatic

## Version History

- **v3.2.4** - XSS fix in OAuth2 endpoint
- **v3.2.3** - Complete command injection fix
- **v3.2.2** - Initial security patches
- **v3.2.1** - TypeScript architecture fix

## Acknowledgments

Thanks to GitHub Security Scanning for identifying this vulnerability. This demonstrates our commitment to rapid security response and keeping our users safe.

## Support

- **Issues**: https://github.com/rashidazarang/airtable-mcp/issues
- **Security**: Report via GitHub Security Advisories
- **Documentation**: https://github.com/rashidazarang/airtable-mcp

---

**⚠️ All users should update to v3.2.4 immediately for security protection.**
```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.2.4] - 2025-09-09

### Security Fix
- **Fixed XSS vulnerability in OAuth2 endpoint** (GitHub Security Alert #10, line 708)
  - Added explicit UTF-8 encoding for HTML responses
  - Enhanced Content-Type header with charset specification
  - Added Cache-Control headers to prevent caching of sensitive pages
  - Ensured proper encoding when sending HTML content

## [3.2.3] - 2025-09-09

### Security Fix - Complete Resolution
- **Critical**: Fully resolved command injection vulnerability in `test_client.py` (GitHub Security Alert #10)
  - Added validation for BASE_ID environment variable at startup
  - Eliminated string interpolation vulnerability in API calls
  - Enhanced endpoint validation with path traversal protection
  - Added token format validation
  - Removed all potential injection vectors

## [3.2.2] - 2025-09-09

### Security Fix
- **Critical**: Fixed command injection vulnerability in `test_client.py`
  - Added input validation for API endpoints
  - Removed unused subprocess import
  - Sanitized endpoint parameters to prevent injection attacks

### Changed
- Updated README with latest version information
- Added project structure documentation

## [3.2.1] - 2025-09-09

### Critical Fix - TypeScript Architecture
**IMPORTANT**: This release fixes a critical TypeScript compilation issue that prevented the TypeScript implementation from building correctly.

### Fixed
- **TypeScript Architecture**: Resolved fundamental issue where `.d.ts` files contained runtime code
  - Moved error classes to `errors.ts`
  - Moved tool schemas to `tools-schemas.ts`  
  - Moved AI prompt templates to `prompt-templates.ts`
  - Type definition files now only contain type definitions as per TypeScript best practices
- **Build System**: TypeScript now compiles successfully without errors
- **Import Structure**: Fixed import statements to properly distinguish between type imports and runtime imports

### Changed
- Updated Smithery configuration version to match package.json (3.2.0)

### Verified
- JavaScript implementation: ✅ Working
- TypeScript implementation: ✅ Working (after fixes)
- NPM package installation: ✅ Working
- All entry points: ✅ Working

### Backwards Compatibility
- No breaking changes for existing users
- All existing functionality preserved
- Both JavaScript and TypeScript implementations fully operational

## [3.2.0] - 2025-09-09

### Added
- World-class project structure with proper separation of concerns
- Comprehensive build system with TypeScript support
- Jest testing framework configuration
- ESLint and Prettier for code quality
- Proper CI/CD pipeline structure
- Consolidated documentation in organized directories

### Changed
- Reorganized source code by language (TypeScript, JavaScript, Python)
- Updated package.json with proper scripts and dependencies
- Moved documentation to dedicated docs/ directory
- Improved build and development workflows

### Fixed
- Removed broken symbolic link
- Fixed inconsistent version numbering
- Resolved missing dist/ directory issues

## [3.1.0] - Previous Release
- TypeScript support with comprehensive type definitions
- Enterprise-grade features and automation
- AI-powered analytics and predictive modeling

## [1.6.0] - Previous Release
- Enhanced Python implementation
- Improved error handling
- Better Claude Desktop integration

## [1.5.0] - Previous Release
- Multi-language support (JavaScript, TypeScript, Python)
- Advanced Airtable operations
- Comprehensive testing suite

## [1.4.0] - Previous Release
- Initial TypeScript implementation
- Basic CRUD operations
- MCP protocol support

[Full release history available in docs/releases/]
```

--------------------------------------------------------------------------------
/src/python/test_client.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""
Simple test client for Airtable MCP
"""
import asyncio
import json
import os
import sys
import time
from typing import Dict, Any
from urllib.parse import quote

# Load credentials from environment variables
TOKEN = os.environ.get('AIRTABLE_TOKEN', 'YOUR_AIRTABLE_TOKEN_HERE')
BASE_ID = os.environ.get('AIRTABLE_BASE_ID', 'YOUR_BASE_ID_HERE')

if TOKEN == 'YOUR_AIRTABLE_TOKEN_HERE' or BASE_ID == 'YOUR_BASE_ID_HERE':
    print("Error: Please set AIRTABLE_TOKEN and AIRTABLE_BASE_ID environment variables")
    print("Example: export AIRTABLE_TOKEN=your_token_here")
    print("         export AIRTABLE_BASE_ID=your_base_id_here")
    sys.exit(1)

# Validate BASE_ID format to prevent injection
if not all(c.isalnum() or c in '-_' for c in BASE_ID):
    print(f"Error: Invalid BASE_ID format: {BASE_ID}")
    print("BASE_ID should only contain alphanumeric characters, hyphens, and underscores")
    sys.exit(1)

# Validate TOKEN format (basic check)
if not TOKEN or len(TOKEN) < 10:
    print("Error: Invalid AIRTABLE_TOKEN format")
    sys.exit(1)

# Helper function to directly make Airtable API calls
def api_call(endpoint, token=None):
    """Make a direct Airtable API call to test API access
    
    Args:
        endpoint: The API endpoint path (will be validated)
        token: The API token (will use global TOKEN if not provided)
    """
    import requests
    from urllib.parse import quote
    
    # Use global token if not provided
    if token is None:
        token = TOKEN
    
    # Validate and sanitize the endpoint to prevent injection
    if not isinstance(endpoint, str):
        raise ValueError("Endpoint must be a string")
    
    # Remove any potentially dangerous characters and validate format
    # Airtable endpoints should only contain alphanumeric, /, -, and _
    if not all(c.isalnum() or c in '/-_' for c in endpoint):
        raise ValueError(f"Invalid endpoint format: {endpoint}")
    
    # Additional validation: no double slashes, no dots (path traversal)
    if '//' in endpoint or '..' in endpoint:
        raise ValueError(f"Invalid endpoint format: {endpoint}")
    
    # Validate token format
    if not token or not isinstance(token, str) or len(token) < 10:
        raise ValueError("Invalid token format")
    
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    
    # Use proper URL construction to prevent injection
    # Each part is validated separately
    base_url = "https://api.airtable.com/v0"
    # Remove leading/trailing slashes and construct safely
    clean_endpoint = endpoint.strip('/')
    url = f"{base_url}/{clean_endpoint}"
    
    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        return response.json()
    except Exception as e:
        print(f"API error: {str(e)}")
        return {"error": str(e)}

async def main():
    # Instead of using the MCP, let's directly test the Airtable API
    print("Testing direct API access...")
    
    # List bases
    print("\nListing bases:")
    result = api_call("meta/bases")
    if "error" in result:
        print(f"Error: {result['error']}")
    else:
        bases = result.get("bases", [])
        for i, base in enumerate(bases):
            print(f"{i+1}. {base['name']} (ID: {base['id']})")
    
    # List tables in the specified base
    # Construct endpoint safely without string interpolation vulnerabilities
    print(f"\nListing tables in base {BASE_ID}:")
    # BASE_ID is already validated, but we'll construct the path safely
    endpoint = "meta/bases/" + BASE_ID + "/tables"
    result = api_call(endpoint)
    if "error" in result:
        print(f"Error: {result['error']}")
    else:
        tables = result.get("tables", [])
        for i, table in enumerate(tables):
            print(f"{i+1}. {table['name']} (ID: {table['id']}, Fields: {len(table.get('fields', []))})")
            # Print fields
            print("   Fields:")
            for field in table.get('fields', []):
                print(f"   - {field['name']} ({field['type']})")

if __name__ == "__main__":
    asyncio.run(main()) 
```

--------------------------------------------------------------------------------
/docs/guides/ENHANCED_FEATURES.md:
--------------------------------------------------------------------------------

```markdown
# Enhanced MCP Features v2.2.0

This document outlines the comprehensive Model Context Protocol (MCP) 2024-11-05 implementation in Airtable MCP Server v2.2.0.

## 🚀 Complete MCP Protocol Support

### ✅ Implemented Features

#### 1. **Prompts** (`prompts/list`, `prompts/get`)
AI-powered prompt templates for common Airtable operations:
- `analyze_data` - Analyze data patterns and provide insights
- `create_report` - Generate comprehensive reports 
- `data_insights` - Discover hidden insights and correlations
- `optimize_workflow` - Suggest workflow optimizations

#### 2. **Sampling** (`sampling/createMessage`)
Enable AI-powered responses and agentic behaviors:
- Request LLM assistance for complex data analysis
- Support for model preferences and context
- Structured response format with stop reasons

#### 3. **Roots** (`roots/list`)
Filesystem boundary management:
- `/airtable-exports` - Export data access
- `/airtable-attachments` - Attachment file access
- Client-controlled filesystem permissions

#### 4. **Logging** (`logging/setLevel`)
Comprehensive structured logging:
- Dynamic log level adjustment (ERROR, WARN, INFO, DEBUG, TRACE)
- JSON-serializable log messages
- Client-controlled verbosity

#### 5. **OAuth2 Authentication**
Production-ready OAuth2 with PKCE:
- Authorization endpoint: `/oauth/authorize`
- Token endpoint: `/oauth/token`
- PKCE code challenge support
- Secure token management

## 🎯 Trust Score Impact

These implementations directly address the missing MCP protocol features identified in our Trust Score analysis:

### Before (54/100):
- Core MCP protocol: 20/40 (missing features)
- Limited protocol compliance

### Expected After (84+/100):
- Core MCP protocol: 35+/40 (complete implementation)
- Full MCP 2024-11-05 specification compliance
- Enterprise security features
- Professional authentication

## 📊 Protocol Compliance Matrix

| Feature | Status | Implementation |
|---------|--------|----------------|
| Tools | ✅ Complete | 6 core Airtable operations |
| Prompts | ✅ Complete | 4 AI-powered templates |
| Sampling | ✅ Complete | LLM integration ready |
| Roots | ✅ Complete | Filesystem boundary control |
| Resources | ✅ Complete | Subscribe & list changed |
| Logging | ✅ Complete | Dynamic level control |
| OAuth2 | ✅ Complete | PKCE flow implementation |

## 🔧 Usage Examples

### Prompts Usage
```javascript
// List available prompts
{"jsonrpc": "2.0", "id": 1, "method": "prompts/list"}

// Get data analysis prompt
{
  "jsonrpc": "2.0", 
  "id": 2, 
  "method": "prompts/get",
  "params": {
    "name": "analyze_data",
    "arguments": {
      "table": "Sales Data",
      "analysis_type": "trends"
    }
  }
}
```

### Sampling Usage
```javascript
// Request AI assistance
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "sampling/createMessage",
  "params": {
    "messages": [{
      "role": "user",
      "content": {"type": "text", "text": "Analyze my Airtable data"}
    }],
    "modelPreferences": {"model": "claude-3-sonnet"}
  }
}
```

### OAuth2 Flow
```bash
# 1. Authorization
GET /oauth/authorize?client_id=myapp&redirect_uri=http://localhost:3000/callback&code_challenge=xyz&code_challenge_method=S256&state=abc123

# 2. Token exchange
POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=auth_code&code_verifier=xyz&client_id=myapp
```

## 🔒 Security Features

- **Rate Limiting**: 60 requests per minute per client
- **Input Validation**: Sanitization of all user inputs
- **Security Headers**: XSS protection, frame denial, content type
- **OAuth2 PKCE**: Proof Key for Code Exchange security
- **Secure Tokens**: Cryptographically secure token generation

## 🎉 Trust Score Boost Strategy

This enhanced implementation targets the specific areas identified in our Trust Score analysis:

1. **Protocol Implementation** (+15 points)
   - Complete MCP 2024-11-05 specification
   - All major protocol features implemented

2. **Security & Authentication** (+10 points)
   - OAuth2 with PKCE implementation
   - Enterprise security features

3. **Professional Quality** (+5 points)
   - Comprehensive error handling
   - Production-ready code structure
   - Enhanced documentation

**Target: 84+/100 Trust Score** 🎯
```

--------------------------------------------------------------------------------
/src/python/inspector.py:
--------------------------------------------------------------------------------

```python
#!/usr/bin/env python3
"""
MCP Tool Inspector
-----------------
A simple script to list tools in a format Smithery can understand
"""
import json

# Define the tools manually
tools = [
    {
        "name": "list_bases",
        "description": "List all accessible Airtable bases",
        "parameters": {
            "type": "object",
            "properties": {},
            "required": []
        },
        "returns": {
            "type": "string"
        }
    },
    {
        "name": "list_tables",
        "description": "List all tables in the specified base or the default base",
        "parameters": {
            "type": "object",
            "properties": {
                "base_id": {
                    "type": "string",
                    "description": "Optional base ID to use instead of the default"
                }
            },
            "required": []
        },
        "returns": {
            "type": "string"
        }
    },
    {
        "name": "list_records",
        "description": "List records from a table with optional filtering",
        "parameters": {
            "type": "object",
            "properties": {
                "table_name": {
                    "type": "string",
                    "description": "Name of the table to list records from"
                },
                "max_records": {
                    "type": "integer",
                    "description": "Maximum number of records to return (default: 100)"
                },
                "filter_formula": {
                    "type": "string",
                    "description": "Optional Airtable formula to filter records"
                }
            },
            "required": ["table_name"]
        },
        "returns": {
            "type": "string"
        }
    },
    {
        "name": "get_record",
        "description": "Get a specific record from a table",
        "parameters": {
            "type": "object",
            "properties": {
                "table_name": {
                    "type": "string",
                    "description": "Name of the table"
                },
                "record_id": {
                    "type": "string",
                    "description": "ID of the record to retrieve"
                }
            },
            "required": ["table_name", "record_id"]
        },
        "returns": {
            "type": "string"
        }
    },
    {
        "name": "create_records",
        "description": "Create records in a table from JSON string",
        "parameters": {
            "type": "object",
            "properties": {
                "table_name": {
                    "type": "string",
                    "description": "Name of the table"
                },
                "records_json": {
                    "type": "string",
                    "description": "JSON string containing the records to create"
                }
            },
            "required": ["table_name", "records_json"]
        },
        "returns": {
            "type": "string"
        }
    },
    {
        "name": "update_records",
        "description": "Update records in a table from JSON string",
        "parameters": {
            "type": "object",
            "properties": {
                "table_name": {
                    "type": "string",
                    "description": "Name of the table"
                },
                "records_json": {
                    "type": "string",
                    "description": "JSON string containing the records to update with IDs"
                }
            },
            "required": ["table_name", "records_json"]
        },
        "returns": {
            "type": "string"
        }
    },
    {
        "name": "set_base_id",
        "description": "Set the current Airtable base ID",
        "parameters": {
            "type": "object",
            "properties": {
                "base_id": {
                    "type": "string",
                    "description": "Base ID to set as the current base"
                }
            },
            "required": ["base_id"]
        },
        "returns": {
            "type": "string"
        }
    }
]

# Print the tools as JSON
print(json.dumps({"tools": tools}, indent=2)) 
```

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

```typescript
/**
 * Runtime AI prompt templates for Airtable MCP Server
 */

import type { PromptSchema } from './index';

export const AI_PROMPT_TEMPLATES: Record<string, PromptSchema> = {
  analyze_data: {
    name: 'analyze_data',
    description: 'Advanced AI data analysis with statistical insights, pattern recognition, and predictive modeling',
    arguments: [
      { name: 'table', description: 'Table name or ID to analyze', required: true, type: 'string' },
      { name: 'analysis_type', description: 'Type of analysis', required: false, type: 'string', enum: ['trends', 'statistical', 'patterns', 'predictive', 'anomaly_detection', 'correlation_matrix'] },
      { name: 'field_focus', description: 'Specific fields to focus the analysis on', required: false, type: 'string' },
      { name: 'time_dimension', description: 'Time field for temporal analysis', required: false, type: 'string' },
      { name: 'confidence_level', description: 'Statistical confidence level', required: false, type: 'number', enum: ['0.90', '0.95', '0.99'] }
    ]
  },
  
  create_report: {
    name: 'create_report',
    description: 'Intelligent report generation with business insights and stakeholder-specific recommendations',
    arguments: [
      { name: 'table', description: 'Table name or ID for report data', required: true, type: 'string' },
      { name: 'report_type', description: 'Type of report to generate', required: true, type: 'string', enum: ['executive_summary', 'detailed_analysis', 'dashboard', 'stakeholder_report'] },
      { name: 'target_audience', description: 'Primary audience for the report', required: true, type: 'string', enum: ['executives', 'managers', 'analysts', 'technical_team'] },
      { name: 'include_recommendations', description: 'Include actionable recommendations', required: false, type: 'boolean' },
      { name: 'time_period', description: 'Time period for analysis', required: false, type: 'string' },
      { name: 'format_preference', description: 'Preferred report format', required: false, type: 'string', enum: ['narrative', 'bullet_points', 'charts', 'mixed'] }
    ]
  },
  
  predictive_analytics: {
    name: 'predictive_analytics',
    description: 'Advanced forecasting and trend prediction with multiple algorithms and uncertainty quantification',
    arguments: [
      { name: 'table', description: 'Table name or ID for prediction', required: true, type: 'string' },
      { name: 'target_field', description: 'Field to predict', required: true, type: 'string' },
      { name: 'prediction_periods', description: 'Number of periods to predict', required: false, type: 'number' },
      { name: 'algorithm', description: 'Prediction algorithm to use', required: false, type: 'string', enum: ['linear_regression', 'arima', 'exponential_smoothing', 'random_forest', 'neural_network'] },
      { name: 'include_confidence_intervals', description: 'Include confidence intervals', required: false, type: 'boolean' },
      { name: 'historical_periods', description: 'Historical periods for training', required: false, type: 'number' },
      { name: 'external_factors', description: 'External factors to consider', required: false, type: 'string' },
      { name: 'business_context', description: 'Business context for predictions', required: false, type: 'string' }
    ]
  },
  
  natural_language_query: {
    name: 'natural_language_query',
    description: 'Process natural language questions about data with intelligent context awareness',
    arguments: [
      { name: 'question', description: 'Natural language question about the data', required: true, type: 'string' },
      { name: 'tables', description: 'Specific tables to search (optional)', required: false, type: 'string' },
      { name: 'response_format', description: 'Preferred response format', required: false, type: 'string', enum: ['natural_language', 'structured_data', 'visualization_ready', 'action_items'] },
      { name: 'context_awareness', description: 'Use context from previous queries', required: false, type: 'boolean' },
      { name: 'confidence_threshold', description: 'Minimum confidence for responses', required: false, type: 'number' },
      { name: 'clarifying_questions', description: 'Ask clarifying questions if needed', required: false, type: 'boolean' }
    ]
  }
};
```

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

```typescript
/**
 * Basic TypeScript Usage Example
 * Demonstrates type-safe Airtable MCP operations
 */

import {
  AirtableMCPServer,
  MCPServerCapabilities,
  ListRecordsInput,
  CreateRecordInput,
  AnalyzeDataPrompt
} from '@rashidazarang/airtable-mcp/types';

// Type-safe server initialization
async function initializeServer(): Promise<void> {
  const server = new AirtableMCPServer();
  
  const capabilities: MCPServerCapabilities = {
    tools: { listChanged: false },
    prompts: { listChanged: false },
    resources: { subscribe: false, listChanged: false },
    roots: { listChanged: false },
    sampling: {},
    logging: {}
  };
  
  const serverInfo = await server.initialize(capabilities);
  console.log('Server initialized:', serverInfo);
}

// Type-safe data operations
async function performDataOperations(): Promise<void> {
  const server = new AirtableMCPServer();
  
  // List records with type safety
  const listParams: ListRecordsInput = {
    table: 'Tasks',
    maxRecords: 10,
    filterByFormula: "Status = 'Active'"
  };
  
  const records = await server.handleToolCall('list_records', listParams);
  console.log('Records retrieved:', records);
  
  // Create record with validated types
  const createParams: CreateRecordInput = {
    table: 'Tasks',
    fields: {
      'Name': 'New Task',
      'Status': 'Active',
      'Priority': 'High',
      'Due Date': new Date().toISOString()
    },
    typecast: true
  };
  
  const newRecord = await server.handleToolCall('create_record', createParams);
  console.log('Record created:', newRecord);
}

// Type-safe AI prompt usage
async function useAIPrompts(): Promise<void> {
  const server = new AirtableMCPServer();
  
  // Advanced data analysis with strict typing
  const analysisParams: AnalyzeDataPrompt = {
    table: 'Sales',
    analysis_type: 'predictive',
    field_focus: 'revenue,conversion_rate',
    time_dimension: 'created_date',
    confidence_level: 0.95
  };
  
  const analysis = await server.handlePromptGet('analyze_data', analysisParams);
  console.log('AI Analysis:', analysis);
  
  // Type-safe error handling
  try {
    // This will cause a TypeScript compile error if types don't match
    const invalidParams = {
      table: 'Sales',
      analysis_type: 'invalid_type', // TypeScript will catch this!
      confidence_level: 1.5 // TypeScript will catch this too!
    };
    
    // await server.handlePromptGet('analyze_data', invalidParams);
  } catch (error) {
    console.error('Type-safe error handling:', error);
  }
}

// Enterprise-grade type validation
interface BusinessMetrics {
  revenue: number;
  conversion_rate: number;
  customer_count: number;
  timestamp: Date;
}

function validateBusinessMetrics(data: unknown): data is BusinessMetrics {
  const metrics = data as BusinessMetrics;
  return (
    typeof metrics.revenue === 'number' &&
    typeof metrics.conversion_rate === 'number' &&
    typeof metrics.customer_count === 'number' &&
    metrics.timestamp instanceof Date
  );
}

// Type-safe configuration
interface AppConfig {
  airtable: {
    token: string;
    baseId: string;
  };
  server: {
    port: number;
    host: string;
    logLevel: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
  };
  ai: {
    enablePredictiveAnalytics: boolean;
    confidenceThreshold: number;
    maxAnalysisFields: number;
  };
}

const config: AppConfig = {
  airtable: {
    token: process.env.AIRTABLE_TOKEN!,
    baseId: process.env.AIRTABLE_BASE_ID!
  },
  server: {
    port: 8010,
    host: 'localhost',
    logLevel: 'INFO'
  },
  ai: {
    enablePredictiveAnalytics: true,
    confidenceThreshold: 0.85,
    maxAnalysisFields: 10
  }
};

// Main execution with comprehensive error handling
async function main(): Promise<void> {
  try {
    console.log('🚀 Starting TypeScript Airtable MCP Example');
    
    await initializeServer();
    await performDataOperations();
    await useAIPrompts();
    
    console.log('✅ All operations completed successfully with type safety!');
  } catch (error) {
    console.error('❌ Error occurred:', error);
    process.exit(1);
  }
}

// Export for testing and reuse
export {
  initializeServer,
  performDataOperations,
  useAIPrompts,
  validateBusinessMetrics,
  BusinessMetrics,
  AppConfig
};

// Run if this file is executed directly
if (require.main === module) {
  main();
}
```

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

```typescript
import { createHash } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
import {
  QueryInput,
  QueryOutput,
  queryInputSchema,
  queryInputShape,
  queryOutputSchema
} from '../types';
import { AppContext } from '../context';
import { handleToolError } from './handleError';

type PiiPolicy = {
  field: string;
  policy: 'mask' | 'hash' | 'drop';
};

function maskValue(value: unknown): unknown {
  if (value === null || value === undefined) {
    return value;
  }
  if (Array.isArray(value)) {
    return value.map(() => '••••');
  }
  if (typeof value === 'object') {
    return '[redacted]';
  }
  return '••••';
}

function hashValue(value: unknown): string {
  const serialized =
    typeof value === 'string' ? value : JSON.stringify(value ?? '');
  return createHash('sha256').update(serialized).digest('hex');
}

function applyPiiPolicies(
  fields: Record<string, unknown>,
  policies: PiiPolicy[]
): Record<string, unknown> {
  if (!policies.length) {
    return fields;
  }
  const result: Record<string, unknown> = { ...fields };
  for (const policy of policies) {
    if (!(policy.field in result)) continue;
    switch (policy.policy) {
      case 'drop':
        delete result[policy.field];
        break;
      case 'mask':
        result[policy.field] = maskValue(result[policy.field]);
        break;
      case 'hash':
        result[policy.field] = hashValue(result[policy.field]);
        break;
      default:
        break;
    }
  }
  return result;
}

export function registerQueryTool(server: McpServer, ctx: AppContext): void {
  server.registerTool(
    'query',
    {
      description: 'Query Airtable records with filtering, sorting, and pagination.',
      inputSchema: queryInputShape,
      outputSchema: queryOutputSchema.shape
    },
    async (args: QueryInput, _extra: unknown) => {
      try {
        const input = queryInputSchema.parse(args);
        ctx.governance.ensureOperationAllowed('query');
        ctx.governance.ensureBaseAllowed(input.baseId);
        ctx.governance.ensureTableAllowed(input.baseId, input.table);

        const logger = ctx.logger.child({
          tool: 'query',
          baseId: input.baseId,
          table: input.table
        });

        const queryParams: Record<string, string | number | boolean | Array<string>> = {};

        if (input.fields) {
          queryParams.fields = input.fields;
        }
        if (input.filterByFormula) {
          queryParams.filterByFormula = input.filterByFormula;
        }
        if (input.view) {
          queryParams.view = input.view;
        }
        if (input.pageSize) {
          queryParams.pageSize = input.pageSize;
        }
        if (input.maxRecords) {
          queryParams.maxRecords = input.maxRecords;
        }
        if (input.offset) {
          queryParams.offset = input.offset;
        }
        if (typeof input.returnFieldsByFieldId === 'boolean') {
          queryParams.returnFieldsByFieldId = input.returnFieldsByFieldId;
        }

        if (input.sorts) {
          input.sorts.forEach((sort, index) => {
            queryParams[`sort[${index}][field]`] = sort.field;
            queryParams[`sort[${index}][direction]`] = sort.direction ?? 'asc';
          });
        }

        const response: any = await ctx.airtable.queryRecords(input.baseId, input.table, queryParams);
        const rawRecords: Array<Record<string, unknown>> = Array.isArray(response?.records)
          ? response.records
          : [];

        const piiPolicies = ctx.governance.listPiiPolicies(input.baseId, input.table) as PiiPolicy[];

        const sanitizedRecords = rawRecords.map((record) => {
          const fields = typeof record.fields === 'object' && record.fields !== null ? record.fields : {};
          return {
            id: String(record.id ?? ''),
            createdTime: record.createdTime ? String(record.createdTime) : undefined,
            fields: applyPiiPolicies(fields as Record<string, unknown>, piiPolicies)
          };
        });

        const structuredContent: QueryOutput = {
          records: sanitizedRecords,
          offset: typeof response?.offset === 'string' ? response.offset : undefined,
          summary: {
            returned: sanitizedRecords.length,
            hasMore: Boolean(response?.offset)
          }
        };

        logger.debug('Query completed', {
          returned: sanitizedRecords.length,
          hasMore: structuredContent.summary?.hasMore
        });

        return {
          structuredContent,
          content: [] as const
        };
      } catch (error) {
        return handleToolError('query', error, ctx);
      }
    }
  );
}

```

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

```python
#!/usr/bin/env python3
"""
Simple Airtable MCP Server for Claude
-------------------------------------
A minimal MCP server that implements Airtable tools and Claude's special methods
"""
import os
import sys
import json
import logging
import requests
import traceback
from typing import Dict, Any, List, Optional

# Check if MCP SDK is installed
try:
    from mcp.server.fastmcp import FastMCP
except ImportError:
    print("Error: MCP SDK not found. Please install with 'pip install mcp'")
    sys.exit(1)

# Parse command line arguments
if len(sys.argv) < 5:
    print("Usage: python3 simple_airtable_server.py --token YOUR_TOKEN --base YOUR_BASE_ID")
    sys.exit(1)

# Get the token and base ID from command line arguments
token = None
base_id = None
for i in range(1, len(sys.argv)):
    if sys.argv[i] == "--token" and i+1 < len(sys.argv):
        token = sys.argv[i+1]
    elif sys.argv[i] == "--base" and i+1 < len(sys.argv):
        base_id = sys.argv[i+1]

if not token:
    print("Error: No Airtable token provided. Use --token parameter.")
    sys.exit(1)

if not base_id:
    print("Error: No base ID provided. Use --base parameter.")
    sys.exit(1)

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("airtable-mcp")

# Create MCP server
app = FastMCP("Airtable Tools")

# Helper function for Airtable API calls
async def airtable_api_call(endpoint, method="GET", data=None, params=None):
    """Make an Airtable API call with error handling"""
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    
    url = f"https://api.airtable.com/v0/{endpoint}"
    
    try:
        if method == "GET":
            response = requests.get(url, headers=headers, params=params)
        elif method == "POST":
            response = requests.post(url, headers=headers, json=data)
        else:
            raise ValueError(f"Unsupported method: {method}")
        
        response.raise_for_status()
        return response.json()
    except Exception as e:
        logger.error(f"API call error: {str(e)}")
        return {"error": str(e)}

# Claude-specific methods
@app.rpc_method("resources/list")
async def resources_list(params: Dict = None) -> Dict:
    """List available Airtable resources for Claude"""
    try:
        # Return a simple list of resources
        resources = [
            {"id": "airtable_tables", "name": "Airtable Tables", "description": "Tables in your Airtable base"}
        ]
        return {"resources": resources}
    except Exception as e:
        logger.error(f"Error in resources/list: {str(e)}")
        return {"error": {"code": -32000, "message": str(e)}}

@app.rpc_method("prompts/list")
async def prompts_list(params: Dict = None) -> Dict:
    """List available prompts for Claude"""
    try:
        # Return a simple list of prompts
        prompts = [
            {"id": "tables_prompt", "name": "List Tables", "description": "List all tables"}
        ]
        return {"prompts": prompts}
    except Exception as e:
        logger.error(f"Error in prompts/list: {str(e)}")
        return {"error": {"code": -32000, "message": str(e)}}

# Airtable tool functions
@app.tool()
async def list_tables() -> str:
    """List all tables in the specified base"""
    try:
        result = await airtable_api_call(f"meta/bases/{base_id}/tables")
        
        if "error" in result:
            return f"Error: {result['error']}"
        
        tables = result.get("tables", [])
        if not tables:
            return "No tables found in this base."
        
        table_list = [f"{i+1}. {table['name']} (ID: {table['id']})" 
                    for i, table in enumerate(tables)]
        return "Tables in this base:\n" + "\n".join(table_list)
    except Exception as e:
        return f"Error listing tables: {str(e)}"

@app.tool()
async def list_records(table_name: str, max_records: int = 100) -> str:
    """List records from a table"""
    try:
        params = {"maxRecords": max_records}
        result = await airtable_api_call(f"{base_id}/{table_name}", params=params)
        
        if "error" in result:
            return f"Error: {result['error']}"
        
        records = result.get("records", [])
        if not records:
            return "No records found in this table."
        
        # Format the records for display
        formatted_records = []
        for i, record in enumerate(records):
            record_id = record.get("id", "unknown")
            fields = record.get("fields", {})
            field_text = ", ".join([f"{k}: {v}" for k, v in fields.items()])
            formatted_records.append(f"{i+1}. ID: {record_id} - {field_text}")
        
        return "Records:\n" + "\n".join(formatted_records)
    except Exception as e:
        return f"Error listing records: {str(e)}"

# Start the server
if __name__ == "__main__":
    print(f"Starting Airtable MCP Server with token {token[:5]}...{token[-5:]} and base {base_id}")
    app.start() 
```

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

```javascript
#!/usr/bin/env node

/**
 * Comprehensive Test Script for Airtable MCP
 * Tests all available MCP tools and functionality
 */

const http = require('http');

const MCP_SERVER_URL = 'http://localhost:8010/mcp';
const TEST_TOKEN = process.env.AIRTABLE_TOKEN || 'YOUR_AIRTABLE_TOKEN_HERE';
const TEST_BASE_ID = process.env.AIRTABLE_BASE_ID || 'YOUR_BASE_ID_HERE';

if (TEST_TOKEN === 'YOUR_AIRTABLE_TOKEN_HERE' || TEST_BASE_ID === 'YOUR_BASE_ID_HERE') {
  console.error('Error: Please set AIRTABLE_TOKEN and AIRTABLE_BASE_ID environment variables');
  console.error('Example: export AIRTABLE_TOKEN=your_token_here');
  console.error('         export AIRTABLE_BASE_ID=your_base_id_here');
  process.exit(1);
}

// Helper function to make MCP requests
function makeMCPRequest(method, params = {}) {
  return new Promise((resolve, reject) => {
    const postData = JSON.stringify({
      jsonrpc: '2.0',
      id: Date.now(),
      method: method,
      params: params
    });

    const options = {
      hostname: 'localhost',
      port: 8010,
      path: '/mcp',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(postData)
      }
    };

    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', (chunk) => {
        data += chunk;
      });
      res.on('end', () => {
        try {
          const response = JSON.parse(data);
          resolve(response);
        } catch (e) {
          reject(new Error(`Failed to parse response: ${e.message}`));
        }
      });
    });

    req.on('error', (e) => {
      reject(new Error(`Request failed: ${e.message}`));
    });

    req.write(postData);
    req.end();
  });
}

async function runComprehensiveTest() {
  console.log('🔌 Airtable MCP Comprehensive Test');
  console.log('===================================');
  console.log(`Server: ${MCP_SERVER_URL}`);
  console.log(`Base ID: ${TEST_BASE_ID}`);
  console.log(`Token: ${TEST_TOKEN.substring(0, 10)}...${TEST_TOKEN.substring(TEST_TOKEN.length - 10)}`);
  console.log('');

  try {
    // Test 1: List Resources
    console.log('📋 Test 1: Listing Resources');
    console.log('----------------------------');
    const resourcesResponse = await makeMCPRequest('resources/list');
    console.log('✅ Resources Response:');
    console.log(JSON.stringify(resourcesResponse, null, 2));
    console.log('');

    // Test 2: List Prompts
    console.log('📝 Test 2: Listing Prompts');
    console.log('-------------------------');
    const promptsResponse = await makeMCPRequest('prompts/list');
    console.log('✅ Prompts Response:');
    console.log(JSON.stringify(promptsResponse, null, 2));
    console.log('');

    // Test 3: List Tables
    console.log('📊 Test 3: Listing Tables');
    console.log('------------------------');
    const tablesResponse = await makeMCPRequest('tools/call', {
      name: 'list_tables'
    });
    console.log('✅ Tables Response:');
    console.log(JSON.stringify(tablesResponse, null, 2));
    console.log('');

    // Test 4: List Records from Requests table
    console.log('📄 Test 4: Listing Records (Requests Table)');
    console.log('-------------------------------------------');
    const recordsResponse = await makeMCPRequest('tools/call', {
      name: 'list_records',
      arguments: {
        table_name: 'requests',
        max_records: 3
      }
    });
    console.log('✅ Records Response:');
    console.log(JSON.stringify(recordsResponse, null, 2));
    console.log('');

    // Test 5: List Records from Providers table
    console.log('👥 Test 5: Listing Records (Providers Table)');
    console.log('--------------------------------------------');
    const providersResponse = await makeMCPRequest('tools/call', {
      name: 'list_records',
      arguments: {
        table_name: 'providers',
        max_records: 3
      }
    });
    console.log('✅ Providers Response:');
    console.log(JSON.stringify(providersResponse, null, 2));
    console.log('');

    // Test 6: List Records from Categories table
    console.log('🏷️ Test 6: Listing Records (Categories Table)');
    console.log('---------------------------------------------');
    const categoriesResponse = await makeMCPRequest('tools/call', {
      name: 'list_records',
      arguments: {
        table_name: 'categories',
        max_records: 3
      }
    });
    console.log('✅ Categories Response:');
    console.log(JSON.stringify(categoriesResponse, null, 2));
    console.log('');

    console.log('🎉 All Tests Completed Successfully!');
    console.log('');
    console.log('📊 Test Summary:');
    console.log('✅ MCP Server is running and accessible');
    console.log('✅ Airtable API connection is working');
    console.log('✅ All MCP tools are functioning properly');
    console.log('✅ JSON-RPC protocol is correctly implemented');
    console.log('✅ Error handling is working');
    console.log('');
    console.log('🚀 The Airtable MCP is ready for use!');

  } catch (error) {
    console.error('❌ Test failed:', error.message);
    process.exit(1);
  }
}

// Run the comprehensive test
runComprehensiveTest();


```

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

```markdown
# Installation

Airtable MCP embeds Airtable database connectivity directly into your AI-powered code editor

## Getting Started

Built by Rashid Azarang,

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.

With this MCP server tool, you can enable AI code editors and agents to have access to:

* List and access all your Airtable bases
* Browse tables, fields, and record data
* Create, read, update, and delete records
* Export and manipulate schemas
* Perform complex queries against your data
* Create data migration mappings
* Analyze and transform your Airtable data

That way, you can simply tell Cursor or any AI code editor with MCP integrations:

"Show me all the tables in my Airtable base"

"Find all records from the Customers table where the status is Active and the last purchase was after January 1st"

"Create a new record in the Products table with these fields..."

"Export the schema of my current Airtable base"

"Help me create a mapping between these two tables for data migration"

---

## Requirements

* Node.js 14+ installed on your machine
* Python 3.10+ installed on your machine (automatically detected)
* Airtable Personal Access Token (API Key)
* MCP Client Application (Cursor, Claude Desktop, Cline, Zed, etc.)

**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.

---

## Installation

### 1. Install via Smithery (Easiest)

The easiest way to install Airtable MCP is through Smithery:

1. Visit [Smithery](https://smithery.ai)
2. Search for "@rashidazarang/airtable-mcp"
3. Click "Install" and follow the prompts to configure with your Airtable token and base ID

### 2. Install via NPX (Alternative)

Another simple way to install and use Airtable MCP is via NPX:

```bash
# Install globally
npm install -g airtable-mcp

# Or use directly with npx (no installation needed)
npx airtable-mcp --token YOUR_AIRTABLE_TOKEN --base YOUR_BASE_ID
```

### 3. Get Your Airtable API Token

1. Log in to your Airtable account
2. Go to your [Account Settings](https://airtable.com/account)
3. Navigate to the "API" section
4. Create a Personal Access Token with appropriate permissions
5. Copy your token to use in the configuration

### 4. Configure Your MCP Client

#### For Cursor:

1. Go to Cursor Settings
2. Navigate to Features, scroll down to MCP Servers and click "Add new MCP server"
3. Give it a unique name (airtable-tools), set type to "command" and set the command to:

**For macOS/Linux/Windows:**
```bash
npx airtable-mcp --token YOUR_AIRTABLE_TOKEN --base YOUR_BASE_ID
```

Replace `YOUR_AIRTABLE_TOKEN` with your Airtable Personal Access Token and `YOUR_BASE_ID` with your default Airtable base ID (optional).

#### For Advanced Users via ~/.cursor/mcp.json:

Edit your `~/.cursor/mcp.json` file to include:

```json
{
  "mcpServers": {
    "airtable-tools": {
      "command": "npx",
      "args": [
        "airtable-mcp",
        "--token", "YOUR_AIRTABLE_TOKEN",
        "--base", "YOUR_BASE_ID"
      ]
    }
  }
}
```

### 5. Verify Connection

1. Restart your MCP client (Cursor, etc.)
2. Create a new query using the Composer Agent with Claude 3.5 Sonnet model
3. Ask something like "List my Airtable bases" or "Show me the tables in my current base"
4. You should see a response with your Airtable data

### 6. For Production Use (Optional)

For continuous availability, you can set up Airtable MCP using PM2:

```bash
# Install PM2 if you don't have it
npm install -g pm2

# Create a PM2 config file
echo 'module.exports = {
  apps: [
    {
      name: "airtable-mcp",
      script: "npx",
      args: [
        "airtable-mcp",
        "--token", "YOUR_AIRTABLE_TOKEN",
        "--base", "YOUR_BASE_ID"
      ],
      env: {
        PATH: process.env.PATH,
      },
    },
  ],
};' > ecosystem.config.js

# Start the process
pm2 start ecosystem.config.js

# Set it to start on boot
pm2 startup
pm2 save
```

---

## Troubleshooting

Here are some common issues and their solutions:

### Error: Unable to connect to Airtable API

- Double-check your Airtable API token is correct and has sufficient permissions
- Verify your internet connection
- Check if Airtable API is experiencing downtime

### Issue: MCP server not connecting

- Make sure Node.js 14+ and Python 3.10+ are installed and in your PATH
- Try specifying a specific version: `npx airtable-mcp@latest`
- Check the Cursor logs for any connection errors

### Error: Base not found

- Verify your base ID is correct
- Make sure your API token has access to the specified base
- Try listing all bases first to confirm access

### Issue: Permission denied errors

- Make sure your token has the necessary permissions for the operations you're trying to perform
- Check if you're attempting operations on tables/bases that your token doesn't have access to

### For more help

- Open an issue on the [GitHub repository](https://github.com/rashidazarang/airtable-mcp/issues)
- Check the Airtable API documentation for any API-specific errors 
```

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

```javascript
#!/usr/bin/env node

const path = require('path');
const { execSync } = require('child_process');
const { spawn } = require('child_process');

// Polyfill for AbortController in older Node.js versions
if (typeof globalThis.AbortController === 'undefined') {
  globalThis.AbortController = class AbortController {
    constructor() {
      this.signal = {
        aborted: false,
        addEventListener: () => {},
        removeEventListener: () => {},
        dispatchEvent: () => true
      };
    }
    abort() {
      this.signal.aborted = true;
    }
  };
  console.log('ℹ️ Added AbortController polyfill for compatibility with older Node.js versions');
}

// Parse command-line arguments
const args = process.argv.slice(2);
let tokenIndex = args.indexOf('--token');
let baseIndex = args.indexOf('--base');
let configIndex = args.indexOf('--config');

// Extract token, base ID and config
const token = tokenIndex !== -1 && tokenIndex + 1 < args.length ? args[tokenIndex + 1] : null;
const baseId = baseIndex !== -1 && baseIndex + 1 < args.length ? args[baseIndex + 1] : null;
const config = configIndex !== -1 && configIndex + 1 < args.length ? args[configIndex + 1] : null;

console.log('🔌 Airtable MCP - Connecting your AI to Airtable');
console.log('-----------------------------------------------');

// Find Python interpreter
const getPythonPath = () => {
  try {
    const whichPython = execSync('which python3.10').toString().trim();
    return whichPython;
  } catch (e) {
    try {
      const whichPython = execSync('which python3').toString().trim();
      return whichPython;
    } catch (e) {
      return 'python';
    }
  }
};

// Check Python version
const checkPythonVersion = (pythonPath) => {
  try {
    const versionStr = execSync(`${pythonPath} --version`).toString().trim();
    const versionMatch = versionStr.match(/Python (\d+)\.(\d+)/);
    if (versionMatch) {
      const major = parseInt(versionMatch[1]);
      const minor = parseInt(versionMatch[2]);
      return (major > 3 || (major === 3 && minor >= 10));
    }
    return false;
  } catch (e) {
    return false;
  }
};

const pythonPath = getPythonPath();

// Verify Python compatibility
if (!checkPythonVersion(pythonPath)) {
  console.error('❌ Error: MCP SDK requires Python 3.10+');
  console.error('Please install Python 3.10 or newer and try again.');
  process.exit(1);
}

// We now use inspector_server.py instead of server.py
const serverScript = path.join(__dirname, 'inspector_server.py');

// Check if the script exists
try {
  require('fs').accessSync(serverScript, require('fs').constants.F_OK);
} catch (e) {
  console.error(`❌ Error: Could not find server script at ${serverScript}`);
  console.error('Please make sure you have the complete package installed.');
  process.exit(1);
}

// Prepare arguments for the Python script
const scriptArgs = [serverScript];
if (token) {
  scriptArgs.push('--token', token);
}
if (baseId) {
  scriptArgs.push('--base', baseId);
}
if (config) {
  scriptArgs.push('--config', config);
  
  // Try to extract and log info from config
  try {
    const configObj = JSON.parse(config);
    if (configObj.airtable_token) {
      console.log('✅ Using API token from config');
    }
    if (configObj.base_id) {
      console.log(`✅ Using base ID from config: ${configObj.base_id}`);
    }
  } catch (e) {
    console.warn('⚠️ Could not parse config JSON, attempting to sanitize...');
    
    // Sanitize config JSON - fix common formatting issues
    try {
      // Remove any unexpected line breaks, extra quotes, and escape characters
      const sanitizedConfig = config
        .replace(/[\r\n]+/g, '')
        .replace(/\\+"/g, '"')
        .replace(/^"/, '')
        .replace(/"$/, '')
        .replace(/\\/g, '');
      
      // Try parsing it
      const configObj = JSON.parse(sanitizedConfig);
      if (configObj) {
        console.log('✅ Successfully sanitized config JSON');
        // Update config with sanitized version
        scriptArgs[scriptArgs.indexOf(config)] = sanitizedConfig;
        config = sanitizedConfig;
        
        if (configObj.airtable_token) {
          console.log('✅ Using API token from sanitized config');
        }
        if (configObj.base_id) {
          console.log(`✅ Using base ID from sanitized config: ${configObj.base_id}`);
        }
      }
    } catch (sanitizeErr) {
      console.warn('⚠️ Could not sanitize config JSON, passing it directly to Python script');
    }
  }
} else {
  if (token) {
    console.log('✅ Using provided API token');
  } else {
    console.log('⚠️ No API token provided, will try to use .env file');
  }

  if (baseId) {
    console.log(`✅ Using base ID: ${baseId}`);
  } else {
    console.log('ℹ️ No base ID provided, will need to set one later');
  }
}

// Execute the Python script
const serverProcess = spawn(pythonPath, scriptArgs, {
  stdio: 'inherit',
});

// Handle process exit
serverProcess.on('close', (code) => {
  if (code !== 0) {
    console.error(`❌ Airtable MCP server exited with code ${code}`);
  }
  process.exit(code);
});

// Handle signals
process.on('SIGINT', () => {
  console.log('\n👋 Shutting down Airtable MCP server...');
  serverProcess.kill('SIGINT');
});

process.on('SIGTERM', () => {
  console.log('\n👋 Shutting down Airtable MCP server...');
  serverProcess.kill('SIGTERM');
}); 
```

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

```markdown
# GitHub Issue Responses

## Issue #7: Personal Access Token Leakage

Thank you for responsibly disclosing this security vulnerability. This has been fixed in v1.2.4.

### Actions Taken:
✅ Removed all hardcoded tokens from test files
✅ Updated code to require environment variables for credentials
✅ Added SECURITY_NOTICE.md with rotation instructions
✅ The exposed tokens have been invalidated

### Changes Made:
- `test_client.py` - Now uses environment variables
- `test_mcp_comprehensive.js` - Now uses environment variables
- Added `.env.example` file for secure configuration
- Updated documentation with security best practices

All users should update to v1.2.4 immediately. The exposed tokens are no longer valid, and users must use their own Airtable credentials.

Thank you for helping improve the security of this project!

---

## Issue #6: [Server Bug] @rashidazarang/airtable-mcp

This issue has been resolved in v1.2.4! 

### Root Cause:
The Smithery configuration was using the Python implementation which had compatibility issues with MCP 1.4.1.

### Solution:
- Updated `smithery.yaml` to use the stable JavaScript implementation (`airtable_simple.js`)
- Fixed authentication flow to properly handle credentials
- Added proper environment variable support

### To fix:
1. Update to v1.2.4: `npm install @rashidazarang/airtable-mcp@latest`
2. Reconfigure with your credentials as shown in the updated README
3. Restart Claude Desktop

The server should now connect properly without the "API key is required" error.

---

## Issue #5: When Using Smithy, throwing 'Streamable HTTP error: Error POSTing to endpoint (HTTP 400): null'

Fixed in v1.2.4! This was the same root cause as Issue #6.

### What was wrong:
- The Python server had MCP compatibility issues
- Authentication wasn't being handled correctly
- The Smithery configuration was misconfigured

### What we fixed:
- Switched to the JavaScript implementation
- Updated smithery.yaml with proper configuration
- Fixed credential passing through Smithery

### How to resolve:
```bash
# Update to latest version
npm install -g @rashidazarang/airtable-mcp@latest

# Or if using Smithery directly:
npx @smithery/cli install @rashidazarang/airtable-mcp --update
```

Then reconfigure with your Airtable credentials. The HTTP 400 errors should be resolved.

---

## Issue #4: Glama listing is missing Dockerfile

Fixed in v1.2.4!

### Changes:
- Created `Dockerfile.node` specifically for Node.js deployment
- Updated `smithery.yaml` to reference the correct Dockerfile
- The original Dockerfile is retained for backward compatibility

The Dockerfile is now included and properly configured for cloud deployments.

---

# GitHub Release Text for v1.2.4

## 🚨 Critical Security Release - v1.2.4

### ⚠️ IMPORTANT SECURITY FIX

This release addresses a **critical security vulnerability** where API tokens were hardcoded in test files. All users should update immediately.

### 🔒 Security Fixes
- **Removed hardcoded API tokens** from all test files (fixes #7)
- Test files now require environment variables for credentials
- Added comprehensive security documentation
- Previously exposed tokens have been invalidated

### 🐛 Bug Fixes
- **Fixed Smithery deployment issues** (fixes #5, #6)
  - Resolved HTTP 400 errors when connecting through Smithery
  - Fixed "API key is required for remote connections" error
  - Switched to stable JavaScript implementation for cloud deployments
- **Added missing Dockerfile** for Glama listing (fixes #4)

### ✨ Improvements
- Added environment variable support for secure credential management
- Improved logging with configurable levels (ERROR, WARN, INFO, DEBUG)
- Enhanced error messages for better debugging
- Updated documentation with clear setup instructions

### 📦 What's Changed
- `test_client.py` - Now uses environment variables
- `test_mcp_comprehensive.js` - Now uses environment variables  
- `airtable_simple.js` - Added env variable and logging support
- `smithery.yaml` - Fixed to use JavaScript implementation
- `Dockerfile.node` - New optimized Docker image for Node.js
- `SECURITY_NOTICE.md` - Important security information
- `README.md` - Complete rewrite with better instructions

### 💔 Breaking Changes
Test files now require environment variables:
```bash
export AIRTABLE_TOKEN="your_token"
export AIRTABLE_BASE_ID="your_base_id"
```

### 📋 Migration Instructions

1. **Update to v1.2.4:**
   ```bash
   npm install -g @rashidazarang/airtable-mcp@latest
   ```

2. **Set up environment variables:**
   ```bash
   export AIRTABLE_TOKEN="your_personal_token"
   export AIRTABLE_BASE_ID="your_base_id"
   ```

3. **Update your MCP configuration** (see README for details)

4. **Restart your MCP client**

### 🙏 Acknowledgments
Special thanks to @BXXC-SDXZ for responsibly disclosing the security vulnerability, and to @ricklesgibson and @punkpeye for reporting the deployment issues.

### ⚠️ Security Note
If you were using the previously exposed tokens, they have been revoked. You must use your own Airtable credentials going forward.

**Full Changelog**: https://github.com/rashidazarang/airtable-mcp/compare/v1.2.3...v1.2.4

---

## NPM Publish Commands

```bash
# Make sure you're logged in to npm
npm login

# Update version (already done in package.json)
npm version 1.2.4

# Publish to npm
npm publish --access public

# Create git tag
git tag -a v1.2.4 -m "Critical security fix and Smithery deployment fixes"
git push origin v1.2.4
```
```

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

```typescript
import fs from 'node:fs';
import path from 'node:path';
import { createHash } from 'node:crypto';
import { config as loadEnv } from 'dotenv';
import { governanceOutputSchema, GovernanceSnapshot } from './types';
import { GovernanceError } from '../errors';

loadEnv();

export type LogLevel = 'error' | 'warn' | 'info' | 'debug';

export interface AirtableAuthConfig {
  personalAccessToken: string;
  patHash: string;
  defaultBaseId?: string;
  allowedBases: string[];
}

export interface AppConfig {
  version: string;
  auth: AirtableAuthConfig;
  governance: GovernanceSnapshot;
  logLevel: LogLevel;
  exceptionQueueSize: number;
}

const DEFAULT_EXCEPTION_QUEUE_SIZE = 500;

function parseCsv(value?: string | null): string[] {
  if (!value) {
    return [];
  }
  return value
    .split(',')
    .map((entry) => entry.trim())
    .filter((entry) => entry.length > 0);
}

function hashSecret(secret: string): string {
  return createHash('sha256').update(secret).digest('hex').slice(0, 12);
}

function resolveLogLevel(): LogLevel {
  const raw = (process.env.LOG_LEVEL || 'info').toLowerCase();
  if (raw === 'error' || raw === 'warn' || raw === 'info' || raw === 'debug') {
    return raw;
  }
  return 'info';
}

function determineAllowedBases(defaultBaseId?: string): string[] {
  const fromEnv = parseCsv(process.env.AIRTABLE_ALLOWED_BASES || process.env.AIRTABLE_BASE_ALLOWLIST);
  const baseSet = new Set<string>();
  if (defaultBaseId) {
    baseSet.add(defaultBaseId);
  }
  fromEnv.forEach((base) => baseSet.add(base));
  // Allow empty base list - users can use list_bases tool to discover bases
  // and then specify them dynamically in tool calls
  return Array.from(baseSet);
}

function parseAllowedTables(raw?: string | null): Array<{ baseId: string; table: string }> {
  if (!raw) {
    return [];
  }
  const tables: Array<{ baseId: string; table: string }> = [];
  for (const entry of raw.split(',')) {
    const trimmed = entry.trim();
    if (!trimmed) continue;
    const [baseId, table] = trimmed.split(':');
    if (!baseId || !table) {
      throw new GovernanceError(
        `Invalid AIRTABLE_ALLOWED_TABLES entry "${trimmed}". Expected format baseId:tableName.`
      );
    }
    tables.push({ baseId: baseId.trim(), table: table.trim() });
  }
  return tables;
}

function readGovernanceFile(): Partial<GovernanceSnapshot> | undefined {
  const explicitPath = process.env.AIRTABLE_GOVERNANCE_PATH;
  const fallbackPath = path.resolve(process.cwd(), 'config', 'governance.json');

  const filePath = explicitPath || fallbackPath;
  if (!fs.existsSync(filePath)) {
    return undefined;
  }

  try {
    const raw = fs.readFileSync(filePath, 'utf8');
    const parsed = JSON.parse(raw);
    const partialSchema = governanceOutputSchema.partial();
    const result = partialSchema.parse(parsed) as Partial<GovernanceSnapshot>;
    return result;
  } catch (error) {
    throw new GovernanceError(
      `Failed to parse governance configuration at ${filePath}: ${error instanceof Error ? error.message : String(error)}`
    );
  }
}

function buildGovernanceSnapshot(allowedBases: string[]): GovernanceSnapshot {
  const baseSnapshot: GovernanceSnapshot = {
    allowedBases,
    allowedTables: [],
    allowedOperations: ['describe', 'query', 'create', 'update', 'upsert'],
    piiFields: [],
    redactionPolicy: 'mask_on_inline',
    loggingPolicy: 'minimal',
    retentionDays: 7
  };

  const overrides = readGovernanceFile();

  const envAllowedTables = parseAllowedTables(process.env.AIRTABLE_ALLOWED_TABLES);

  const merged: GovernanceSnapshot = {
    ...baseSnapshot,
    ...(overrides ?? {})
  };

  // Ensure allow-lists include env tables/bases.
  const bases = new Set<string>(merged.allowedBases);
  allowedBases.forEach((base) => bases.add(base));
  merged.allowedBases = Array.from(bases);

  if (overrides?.allowedTables || envAllowedTables.length > 0) {
    const tableSet = new Map<string, { baseId: string; table: string }>();
    (overrides?.allowedTables ?? []).forEach((table) => {
      tableSet.set(`${table.baseId}:${table.table}`, table);
    });
    envAllowedTables.forEach((table) => {
      tableSet.set(`${table.baseId}:${table.table}`, table);
    });
    merged.allowedTables = Array.from(tableSet.values());
  }

  return governanceOutputSchema.parse(merged);
}

export function loadConfig(): AppConfig {
  const personalAccessToken =
    process.env.AIRTABLE_PAT ||
    process.env.AIRTABLE_TOKEN ||
    process.env.AIRTABLE_API_TOKEN ||
    process.env.AIRTABLE_API_KEY;

  if (!personalAccessToken) {
    throw new GovernanceError(
      'Missing Airtable credentials. Set AIRTABLE_PAT (preferred) or AIRTABLE_TOKEN.'
    );
  }

  const defaultBaseId = process.env.AIRTABLE_DEFAULT_BASE ?? process.env.AIRTABLE_BASE_ID ?? process.env.AIRTABLE_BASE;
  const allowedBases = determineAllowedBases(defaultBaseId);
  const governance = buildGovernanceSnapshot(allowedBases);

  const auth: AirtableAuthConfig = {
    personalAccessToken,
    patHash: hashSecret(personalAccessToken),
    allowedBases
  };
  if (defaultBaseId) {
    auth.defaultBaseId = defaultBaseId;
  }

  return {
    version: process.env.npm_package_version || '0.0.0',
    auth,
    governance,
    logLevel: resolveLogLevel(),
    exceptionQueueSize:
      Number.parseInt(process.env.EXCEPTION_QUEUE_SIZE || '', 10) > 0
        ? Number.parseInt(process.env.EXCEPTION_QUEUE_SIZE as string, 10)
        : DEFAULT_EXCEPTION_QUEUE_SIZE
  };
}

```
Page 1/4FirstPrevNextLast