Skip to main content
Glama
medlag

Airtable MCP Server

by medlag
README.md
# Airtable MCP Server

[![CI](https://github.com/medlag/airtable-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/medlag/airtable-mcp-server/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

A [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude (or any MCP client) **read, create, update, and search records in an Airtable base** through your own API key.

It exposes five tools over stdio:

| Tool | What it does |
| --- | --- |
| `list_records` | List records from a table, with optional view, page size, max records, and an Airtable `filterByFormula`. |
| `get_record` | Fetch a single record by its ID. |
| `create_record` | Create a record from a `fields` object. |
| `update_record` | Update selected fields on an existing record. |
| `search_records` | Case-insensitive substring search on one field. |

## Why

Airtable is where a lot of small teams keep their CRM, content calendar, or ops tracker. This server puts that data one instruction away from Claude — *"add a lead for Acme Corp"*, *"which deals are still open?"* — without exporting anything or writing glue code per question.

## Requirements

- Node.js **18+** (uses the built-in `fetch`)
- An Airtable [personal access token](https://airtable.com/create/tokens) with `data.records:read` and `data.records:write` scopes
- The **base ID** of the base you want to expose (from the base URL: `airtable.com/appXXXXXXXXXXXXXX/...`)

## Install & build

```bash
git clone https://github.com/medlag/airtable-mcp-server.git
cd airtable-mcp-server
npm install
npm run build
```

## Configuration

The server reads two required environment variables:

| Variable | Required | Description |
| --- | --- | --- |
| `AIRTABLE_API_KEY` | yes | Airtable personal access token. |
| `AIRTABLE_BASE_ID` | yes | ID of the base to operate on. |
| `AIRTABLE_API_URL` | no | Override the API base URL (defaults to `https://api.airtable.com/v0`). |

Copy `.env.example` to `.env` for local runs, or set the variables in your MCP client config (below). **The token is only ever read from the environment — it is never logged or written to disk.**

## Use it with Claude Desktop

Add this to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "airtable": {
      "command": "node",
      "args": ["/absolute/path/to/airtable-mcp-server/dist/index.js"],
      "env": {
        "AIRTABLE_API_KEY": "patXXXXXXXXXXXXXX.XXXXXXXX",
        "AIRTABLE_BASE_ID": "appXXXXXXXXXXXXXX"
      }
    }
  }
}
```

Restart Claude Desktop; the five tools appear under the plug icon.

## Use it with Claude Code

```bash
claude mcp add airtable \
  --env AIRTABLE_API_KEY=patXXXXXXXXXXXXXX.XXXXXXXX \
  --env AIRTABLE_BASE_ID=appXXXXXXXXXXXXXX \
  -- node /absolute/path/to/airtable-mcp-server/dist/index.js
```

## Example prompts

Once connected, you can ask things like:

- *"List 10 rows from the **Leads** table."*
- *"Create a record in **Tasks** with Name 'Ship v1' and Status 'In progress'."*
- *"Find every record in **Contacts** whose Company contains 'acme'."*
- *"Mark record recABC123 in **Tasks** as Done."*

> `list_records` and `search_records` return a single Airtable page — up to 100 records. Narrow with `filterByFormula`, a `view`, or `maxRecords` rather than expecting the full table.

## Design notes

- **Typed REST wrapper** — each Airtable endpoint maps to one method in [`src/airtable-client.ts`](src/airtable-client.ts); API errors surface as a structured `AirtableError` with the HTTP status and Airtable's own message.
- **stdout is sacred** — MCP speaks JSON-RPC over stdout, so all logs go to stderr ([`src/logger.ts`](src/logger.ts)).
- **Fail fast on config** — missing env vars throw a clear message at startup instead of a confusing 401 later ([`src/config.ts`](src/config.ts)).
- **Input validation** — every tool argument is validated with [zod](https://zod.dev) before a request is made.

## Development

```bash
npm run dev        # tsc --watch (build config)
npm run typecheck  # type-check src + tests, no emit
npm test           # unit tests (Node's built-in runner via tsx)
```

## Testing

The suite runs on Node's built-in test runner — no test framework dependency — and covers the logic that must not regress:

- **`config`** — missing/blank env vars fail fast; values are trimmed; the API URL override is honoured.
- **`formula`** — `search_records` escapes backslashes and double quotes, and the field name is validated at the tool boundary, so a crafted value or field can't break out of the Airtable formula (injection guard).
- **`airtable-client`** — GET/POST/PATCH shapes, bearer auth, query-string building, and both error paths (non-2xx → `AirtableError` with the API's message and status; transport failure → status `0`). The `fetch` implementation is injected, so these run offline.

CI runs `typecheck → build → test` on Node 18, 20, and 22.

## License

MIT — see [LICENSE](LICENSE).

TDQS

A3.9/5.0

Scored across 5 tools

Disambiguation4/5

Each tool targets a distinct operation: get by ID, list with optional formula, create, update, and substring search. Search and list have some overlap, but their purposes are clearly differentiated by descriptions.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern with snake_case (get_record, list_records, create_record, update_record, search_records), making the set predictable.

Tool Count5/5

Five tools is well-scoped for a focused Airtable CRUD-like server, covering core read and write operations without unnecessary bloat.

Completeness3/5

Missing delete_record is a notable gap in lifecycle coverage. Read (get/list/search), create, and update are present, but the inability to delete records limits full CRUD operations.

Maintenance

ActivityStale
ResponsivenessNo issues