MCP skill for airtable. Provides 8 tools: ping, list_bases, search_bases, list_tables_for_base, get_table_schema, list_records_for_table, create_records_for_table, update_records_for_table
Add this skill
npx mdskills install manojbajaj95/airtableComprehensive Airtable MCP server with 8 well-documented tools and OAuth auth
1---2name: airtable3description: "MCP skill for airtable. Provides 8 tools: ping, list_bases, search_bases, list_tables_for_base, get_table_schema, list_records_for_table, create_records_for_table, update_records_for_table"4---56# airtable78MCP skill for airtable. Provides 8 tools: ping, list_bases, search_bases, list_tables_for_base, get_table_schema, list_records_for_table, create_records_for_table, update_records_for_table910## Authentication1112This MCP server uses **OAuth** authentication.13The OAuth flow is handled automatically by the MCP client. Tokens are persisted14to `~/.mcp-skill/airtable/oauth-tokens/` so subsequent runs reuse the15same credentials without re-authenticating.1617```python18app = AirtableApp() # uses default OAuth flow19```2021To bring your own OAuth provider, pass it via the `auth` argument:2223```python24app = AirtableApp(auth=my_oauth_provider)25```2627## Dependencies2829This skill requires the following Python packages:3031- `mcp-skill`3233Install with uv:3435```bash36uv pip install mcp-skill37```3839Or with pip:4041```bash42pip install mcp-skill43```4445## How to Run4647**Important:** Add `.agents/skills` to your Python path so imports resolve correctly:4849```python50import sys51sys.path.insert(0, ".agents/skills")52from airtable.app import AirtableApp53```5455Or set the `PYTHONPATH` environment variable:5657```bash58export PYTHONPATH=".agents/skills:$PYTHONPATH"59```6061**Preferred: use `uv run`** (handles dependencies automatically):6263```bash64PYTHONPATH=.agents/skills uv run --with mcp-skill python -c "65import asyncio66from airtable.app import AirtableApp6768async def main():69 app = AirtableApp()70 result = await app.ping()71 print(result)7273asyncio.run(main())74"75```7677**Alternative: use `python` directly** (install dependencies first):7879```bash80pip install mcp-skill81PYTHONPATH=.agents/skills python -c "82import asyncio83from airtable.app import AirtableApp8485async def main():86 app = AirtableApp()87 result = await app.ping()88 print(result)8990asyncio.run(main())91"92```9394## Available Tools9596### ping9798Ping the MCP server to check if it is running99100**Example:**101```python102result = await app.ping()103```104105### list_bases106107Lists all bases that you have access to in your Airtable account.108Use this to get the baseId of the base you want to use.109Favorited and recently viewed bases are generally more relevant.110111**Example:**112```python113result = await app.list_bases()114```115116### search_bases117118Searches for bases by name.119This is useful when you need to find a specific base quickly by a partial name-based match.120Returns bases sorted by their relevance score, as well as a recommended base ID and a hint on whether121we need to ask the user to explicitly select the base they want to use.122123| Parameter | Type | Required | Description |124|-----------|------|----------|-------------|125| searchQuery | `str` | Yes | The query to search for bases by name.126The search is case-insensitive and works with partial matches.127Examples: "projects", "issues", "customers" |128129**Example:**130```python131result = await app.search_bases(searchQuery="example")132```133134### list_tables_for_base135136Gets the summary of a specific base. This includes the schemas of all tables in the137base, including field name and type.138139| Parameter | Type | Required | Description |140|-----------|------|----------|-------------|141| baseId | `str` | Yes | The ID of the base to get the summary of.142Must start with "app" and is 14 characters long.143Example: "appZfrNIUEip5MazD".144Do not substitute user-facing names for baseId.145To get baseId, use the search_bases or list_bases tool. |146147**Example:**148```python149result = await app.list_tables_for_base(baseId="example")150```151152### get_table_schema153154Gets the detailed schema information for specified tables and fields in a base.155This returns the field ID, type, and config for the specified fields of the specified tables.156Example: get schema for two fields in a table:157{"baseId": "appZfrNIUEip5MazD", "tables": [{"tableId": "tblGlReoTNWfYnXIG", "fieldIds": ["fld8WsrpLHHevsnW8", "fldgD18XtsueoiguT"]}]}158159| Parameter | Type | Required | Description |160|-----------|------|----------|-------------|161| baseId | `str` | Yes | The ID of the base containing the tables.162Must start with "app" and is 14 characters long.163Example: "appZfrNIUEip5MazD".164Do not substitute user-facing names for baseId.165To get baseId, use the search_bases or list_bases tool. |166| tables | `list[Any]` | Yes | An array of table IDs and corresponding field IDs to get schema information for.167Must start with "tbl" and is 14 characters long.168Example: "tblGlReoTNWfYnXIG".169Do not substitute user-facing names for tableId.170To get tableId, use the list_tables_for_base tool.171Field IDs must start with "fld" and is 14 characters long.172Example: "fldGlRtkBNWfYnPOV".173Do not substitute user-facing names for IDs.174To get fieldId, use the list_tables_for_base tool. |175176**Example:**177```python178result = await app.get_table_schema(baseId="example", tables="value")179```180181### list_records_for_table182183Lists records queried from an Airtable table.184Do not assume baseId and tableId. Obtain these from search_bases → list_tables_for_base.185Do not attempt to pass filterByFormula. Look carefully at the filters parameter.186Pre-requisite: If filtering on select/multiSelect fields, you must call get_table_schema first to get the choice IDs.187Aim to provide at least 6 relevant fields via the 'fieldIds' parameter.188Note: Select and multiSelect field values are returned as objects (e.g., {"id": "sel...", "name": "Option", "color": "blue"}) or arrays of such objects. When writing these values back via create_records_for_table or update_records_for_table, use the plain string name (e.g., "Option") instead of the object.189190| Parameter | Type | Required | Description |191|-----------|------|----------|-------------|192| baseId | `str` | Yes | The ID of the base containing the table.193Must start with "app" and is 14 characters long.194Example: "appZfrNIUEip5MazD".195Do not substitute user-facing names for baseId.196To get baseId, use the search_bases or list_bases tool. |197| tableId | `str` | Yes | The ID of the table to list records from.198Must start with "tbl" and is 14 characters long.199Example: "tblGlReoTNWfYnXIG".200Do not substitute user-facing names for tableId.201To get tableId, use the list_tables_for_base tool. |202| fieldIds | `list[str]` | No | Only data for fields whose IDs are in this list will be included in the result.203Pass in only the fields most useful for the user to see.204If not provided, all fields will be included in the result.205Field IDs must start with "fld" and is 14 characters long.206Example: "fldGlRtkBNWfYnPOV".207Do not substitute user-facing names for IDs.208To get fieldId, use the list_tables_for_base tool. |209| pageSize | `float` | No | The maximum number of records to return in the response.210The server may respond with fewer records than this value when the total set has fewer records than this value.211Defaults to 3000. |212| cursor | `str` | No | The cursor to start from. To begin from the first record, do not include a cursor.213For a subsequent paginated request, include the nextCursor from the previous response. |214| sort | `list[Any]` | No | A list of sort objects that specifies how the records will be ordered.215Each sort object must have a fieldId key specifying the ID of the field to sort on, and an optional direction key that is either "asc" or "desc".216The default direction is "asc".217Records are sorted by the first sort object first, then by the second sort object for records that have the same value for the first sort, and so on.218Example sort by a single field in descending order: [{"fieldId": "fld9x4rqyBSCLzsJM", "direction": "desc"}]219Example sort by two fields, first ascending then descending: [{"fieldId": "fld9x4rqyBSCLzsJM", "direction": "asc"}, {"fieldId": "fldulcCPDVz87Bmnw", "direction": "desc"}] |220| recordIds | `list[str]` | No | An array of record IDs to filter by. Only records with these IDs will be returned.221Must start with "rec" and is 14 characters long.222Example: "recZOTa3BDHxlJNzf".223Do not substitute user-facing names for IDs224To get recordId, use the list_records_for_table tool or display_records_for_table tools. |225| filters | `dict[str, Any]` | No | Describes the filters to apply to the records using a structured format.226Example filter where the value of the field with ID "fld8WsrpLHHevsnW8" is "orange" or the value of the field with ID "fldulcCPDVz87Bmnw" is greater than 5:227{"operator": "or", "operands": [{"operator": "=", "operands": ["fld8WsrpLHHevsnW8", "orange"]}, {"operator": ">", "operands": ["fldulcCPDVz87Bmnw", 5]}]}228Example filter where the value of the collaborator field with ID "fldCRi9oz2vRLcIWr" can be any user in a group with ID "ugpDUVUnftA7H9bG8" and the value of the field with ID "fldgD18XtsueoiguT" equals select option with ID "selha8nGNAT5ATR7P":229{"operator": "and", "operands": [{"operator": "hasAnyOf", "operands": ["fldCRi9oz2vRLcIWr", "ugpDUVUnftA7H9bG8"], "operatorOptions": {"matchGroupsByMembership": true}}, {"operator": "=", "operands": ["fldgD18XtsueoiguT", "selha8nGNAT5ATR7P"]}]}230Example filter for records where a date field is within the past week:231{"operands": [{"operator": "isWithin", "operands": ["fldABC12345678x", {"mode": "pastWeek", "timeZone": "America/New_York"}]}]}232Example filter for records where a field is not empty:233{"operands": [{"operator": "isNotEmpty", "operands": ["fldABC12345678x"]}]} |234235**Example:**236```python237result = await app.list_records_for_table(baseId="example", tableId="example", fieldIds="value")238```239240### create_records_for_table241242Creates new records in an Airtable table.243To get baseId and tableId, use the search_bases and list_tables_for_base tools first.244For select/multiSelect fields, provide the option name as a plain string (e.g., "In progress") or array of strings, not the object format returned by list_records_for_table.245Example: create a record with text, number, select, and multiSelect fields:246{"baseId": "appZfrNIUEip5MazD", "tableId": "tblGlReoTNWfYnXIG", "records": [{"fields": {"fldGlRtkBNWfYnPOV": "Launch meeting", "fldulcCPDVz87Bmnw": 42, "fld8WsrpLHHevsnW8": "In progress", "fldgD18XtsueoiguT": ["Urgent", "Q1"]}}]}247248| Parameter | Type | Required | Description |249|-----------|------|----------|-------------|250| baseId | `str` | Yes | The ID of the base containing the table.251Must start with "app" and is 14 characters long.252Example: "appZfrNIUEip5MazD".253Do not substitute user-facing names for baseId.254To get baseId, use the search_bases or list_bases tool. |255| tableId | `str` | Yes | The ID of the table to create a record in.256Must start with "tbl" and is 14 characters long.257Example: "tblGlReoTNWfYnXIG".258Do not substitute user-facing names for tableId.259To get tableId, use the list_tables_for_base tool. |260| records | `list[Any]` | Yes | An array of record objects to create. Each record must have a "fields" property261containing the field values. You can create up to 10 records per request. |262| typecast | `bool` | No | Whether or not to perform best-effort automatic data conversion from string values.263Defaults to false to preserve data integrity. |264265**Example:**266```python267result = await app.create_records_for_table(baseId="example", tableId="example", records="value")268```269270### update_records_for_table271272Updates records in an Airtable table.273The fields you specify will be updated, and all other fields will be left unchanged.274To get baseId and tableId, consider using the search_bases and list_tables_for_base tools first.275For select/multiSelect fields, provide the option name as a plain string (e.g., "In progress") or array of strings, not the object format returned by list_records_for_table.276Example: update a record's fields:277{"baseId": "appZfrNIUEip5MazD", "tableId": "tblGlReoTNWfYnXIG", "records": [{"id": "recZOTa3BDHxlJNzf", "fields": {"fldGlRtkBNWfYnPOV": "Updated name", "fld8WsrpLHHevsnW8": "Done"}}]}278279| Parameter | Type | Required | Description |280|-----------|------|----------|-------------|281| baseId | `str` | Yes | The ID of the base containing the table.282Must start with "app" and is 14 characters long.283Example: "appZfrNIUEip5MazD".284Do not substitute user-facing names for baseId.285To get baseId, use the search_bases or list_bases tool. |286| tableId | `str` | Yes | The ID of the table to update records in.287Must start with "tbl" and is 14 characters long.288Example: "tblGlReoTNWfYnXIG".289Do not substitute user-facing names for tableId.290To get tableId, use the list_tables_for_base tool. |291| records | `list[Any]` | Yes | An array of record objects to update. Each record must have a "fields" property292containing the field values. You can update up to 10 records per request. |293| performUpsert | `dict[str, Any]` | No | Enables upsert behavior when set.294When upserting is enabled, the recordId parameter is optional.295Records that do not include a recordId will use the fields chosen by the fieldIdsToMergeOn parameter to match with existing records.296- If no matches are found, a new record will be created.297- If a match is found, that record will be updated.298- If multiple matches are found, the request will fail.299Records that include id will ignore fieldIdsToMergeOn and behave as normal updates.300If no record with the given id exists, the request will fail and will not create a new record |301| typecast | `bool` | No | Whether or not to perform best-effort automatic data conversion from string values.302Defaults to false to preserve data integrity. |303304**Example:**305```python306result = await app.update_records_for_table(baseId="example", tableId="example", records="value")307```308309
Full transparency — inspect the skill content before installing.