
Airtable
- 14 installs
- 7 repo stars
- Updated June 23, 2026
- manojbajaj95/mcp-skill
Helps with ai & agent building tasks.
About
airtable is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- airtable
- AI & Agent Building
- AI-coding skill
Airtable by the numbers
- 14 all-time installs (skills.sh)
- Ranked #11,296 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manojbajaj95/mcp-skill --skill airtableAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 7 |
| Last updated | June 23, 2026 |
| Repository | manojbajaj95/mcp-skill ↗ |
What it does
Helps with ai & agent building tasks.
Files
airtable
Use this skill when you need to work with airtable through its generated async Python app, call its MCP-backed functions from code, or inspect available functions with the mcp-skill CLI.
Authentication
This app can use the MCP client's built-in OAuth flow when the server requires it. In most cases, the default constructor is enough. Tokens are persisted to ~/.mcp-skill/auth/ so subsequent runs reuse the same credentials automatically.
app = AirtableApp()If you need a custom OAuth provider, pass it via the auth argument:
app = AirtableApp(auth=my_oauth_provider)Dependencies
This skill requires the following Python packages:
mcp-skill
Install with uv:
uv pip install mcp-skillOr with pip:
pip install mcp-skillPython Usage
Use the generated app directly in async Python code:
import asyncio
from airtable.app import AirtableApp
async def main():
app = AirtableApp()
result = await app.ping()
print(result)
asyncio.run(main())Async Usage Notes
- Every generated tool method is
async, so call it withawait. - Use these apps inside an async function, then run that function with
asyncio.run(...)if you are in a script. - If you forget
await, you will get a coroutine object instead of the actual tool result. - Be careful when mixing this with other event-loop environments such as notebooks, web servers, or async frameworks.
Discover Functions with the CLI
Use the CLI to find available apps, list functions on an app, and inspect a function before calling it:
uvx mcp-skill list-apps
uvx mcp-skill list-functions airtable
uvx mcp-skill inspect airtable pingImportant: Add .agents/skills to your Python path so imports resolve correctly:
import sys
sys.path.insert(0, ".agents/skills")
from airtable.app import AirtableAppOr set the PYTHONPATH environment variable:
export PYTHONPATH=".agents/skills:$PYTHONPATH"Preferred: use `uv run` (handles dependencies automatically):
PYTHONPATH=.agents/skills uv run --with mcp-skill python -c "
import asyncio
from airtable.app import AirtableApp
async def main():
app = AirtableApp()
result = await app.ping()
print(result)
asyncio.run(main())
"Alternative: use `python` directly (install dependencies first):
pip install mcp-skill
PYTHONPATH=.agents/skills python -c "
import asyncio
from airtable.app import AirtableApp
async def main():
app = AirtableApp()
result = await app.ping()
print(result)
asyncio.run(main())
""""Application for interacting with Airtable via MCP."""
from typing import Any
from fastmcp import Client
from mcp_skill.auth import OAuth
import json
class AirtableApp:
"""
Application for interacting with Airtable via MCP.
Provides tools to interact with tools: ping, list_bases, search_bases, list_tables_for_base, get_table_schema and 3 more.
"""
def __init__(self, url: str = "https://mcp.airtable.com/mcp", auth=None) -> None:
self.url = url
self._oauth_auth = auth
def _get_client(self) -> Client:
oauth = self._oauth_auth or OAuth()
return Client(self.url, auth=oauth)
async def ping(self) -> dict[str, Any]:
"""
Ping the MCP server to check if it is running
Returns:
Tool execution result
Tags:
ping
"""
async with self._get_client() as client:
call_args = {}
result = await client.call_tool("ping", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def list_bases(self) -> dict[str, Any]:
"""
Lists all bases that you have access to in your Airtable account.
Use this to get the baseId of the base you want to use.
Favorited and recently viewed bases are generally more relevant.
Returns:
Tool execution result
Tags:
list, bases
"""
async with self._get_client() as client:
call_args = {}
result = await client.call_tool("list_bases", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def search_bases(self, searchQuery: str) -> dict[str, Any]:
"""
Searches for bases by name.
This is useful when you need to find a specific base quickly by a partial name-based match.
Returns bases sorted by their relevance score, as well as a recommended base ID and a hint on whether
we need to ask the user to explicitly select the base they want to use.
Args:
searchQuery: The query to search for bases by name.
The search is case-insensitive and works with partial matches.
Examples: "projects", "issues", "customers"
Returns:
Tool execution result
Tags:
search, bases
"""
async with self._get_client() as client:
call_args = {}
call_args["searchQuery"] = searchQuery
result = await client.call_tool("search_bases", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def list_tables_for_base(self, baseId: str) -> dict[str, Any]:
"""
Gets the summary of a specific base. This includes the schemas of all tables in the
base, including field name and type.
Args:
baseId: The ID of the base to get the summary of.
Must start with "app" and is 14 characters long.
Example: "appZfrNIUEip5MazD".
Do not substitute user-facing names for baseId.
To get baseId, use the search_bases or list_bases tool.
Returns:
Tool execution result
Tags:
list, tables, base
"""
async with self._get_client() as client:
call_args = {}
call_args["baseId"] = baseId
result = await client.call_tool("list_tables_for_base", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def get_table_schema(self, baseId: str, tables: list[Any]) -> dict[str, Any]:
"""
Gets the detailed schema information for specified tables and fields in a base.
This returns the field ID, type, and config for the specified fields of the specified tables.
Example: get schema for two fields in a table:
{"baseId": "appZfrNIUEip5MazD", "tables": [{"tableId": "tblGlReoTNWfYnXIG", "fieldIds": ["fld8WsrpLHHevsnW8", "fldgD18XtsueoiguT"]}]}
Args:
baseId: The ID of the base containing the tables.
Must start with "app" and is 14 characters long.
Example: "appZfrNIUEip5MazD".
Do not substitute user-facing names for baseId.
To get baseId, use the search_bases or list_bases tool.
tables: An array of table IDs and corresponding field IDs to get schema information for.
Must start with "tbl" and is 14 characters long.
Example: "tblGlReoTNWfYnXIG".
Do not substitute user-facing names for tableId.
To get tableId, use the list_tables_for_base tool.
Field IDs must start with "fld" and is 14 characters long.
Example: "fldGlRtkBNWfYnPOV".
Do not substitute user-facing names for IDs.
To get fieldId, use the list_tables_for_base tool.
Returns:
Tool execution result
Tags:
table, schema
"""
async with self._get_client() as client:
call_args = {}
call_args["baseId"] = baseId
call_args["tables"] = tables
result = await client.call_tool("get_table_schema", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def list_records_for_table(self, baseId: str, tableId: str, cursor: str = None, fieldIds: list[str] = None, filters: dict[str, Any] = None, pageSize: float = None, recordIds: list[str] = None, sort: list[Any] = None) -> dict[str, Any]:
"""
Lists records queried from an Airtable table.
Do not assume baseId and tableId. Obtain these from search_bases → list_tables_for_base.
Do not attempt to pass filterByFormula. Look carefully at the filters parameter.
Pre-requisite: If filtering on singleSelect/multipleSelects fields, you must call get_table_schema first to get the choice IDs.
Aim to provide at least 6 relevant fields via the 'fieldIds' parameter.
Note: singleSelect and multipleSelects 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.
Args:
baseId: The ID of the base containing the table.
Must start with "app" and is 14 characters long.
Example: "appZfrNIUEip5MazD".
Do not substitute user-facing names for baseId.
To get baseId, use the search_bases or list_bases tool.
tableId: The ID of the table to list records from.
Must start with "tbl" and is 14 characters long.
Example: "tblGlReoTNWfYnXIG".
Do not substitute user-facing names for tableId.
To get tableId, use the list_tables_for_base tool.
cursor: The cursor to start from. To begin from the first record, do not include a cursor.
For a subsequent paginated request, include the nextCursor from the previous response.
fieldIds: Only data for fields whose IDs are in this list will be included in the result.
Pass in only the fields most useful for the user to see.
If not provided, all fields will be included in the result.
Field IDs must start with "fld" and is 14 characters long.
Example: "fldGlRtkBNWfYnPOV".
Do not substitute user-facing names for IDs.
To get fieldId, use the list_tables_for_base tool.
filters: Describes the filters to apply to the records using a structured format.
Example 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:
{"operator": "or", "operands": [{"operator": "=", "operands": ["fld8WsrpLHHevsnW8", "orange"]}, {"operator": ">", "operands": ["fldulcCPDVz87Bmnw", 5]}]}
Example 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":
{"operator": "and", "operands": [{"operator": "hasAnyOf", "operands": ["fldCRi9oz2vRLcIWr", "ugpDUVUnftA7H9bG8"], "operatorOptions": {"matchGroupsByMembership": true}}, {"operator": "=", "operands": ["fldgD18XtsueoiguT", "selha8nGNAT5ATR7P"]}]}
Example filter for records where a date field is within the past week:
{"operands": [{"operator": "isWithin", "operands": ["fldABC12345678x", {"mode": "pastWeek", "timeZone": "America/New_York"}]}]}
Example filter for records where a field is not empty:
{"operands": [{"operator": "isNotEmpty", "operands": ["fldABC12345678x"]}]}
pageSize: The maximum number of records to return in the response.
The server may respond with fewer records than this value when the total set has fewer records than this value.
Defaults to 3000.
recordIds: An array of record IDs to filter by. Only records with these IDs will be returned.
Must start with "rec" and is 14 characters long.
Example: "recZOTa3BDHxlJNzf".
Do not substitute user-facing names for IDs
To get recordId, use the list_records_for_table tool or display_records_for_table tools.
sort: A list of sort objects that specifies how the records will be ordered.
Each 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".
The default direction is "asc".
Records 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.
Example sort by a single field in descending order: [{"fieldId": "fld9x4rqyBSCLzsJM", "direction": "desc"}]
Example sort by two fields, first ascending then descending: [{"fieldId": "fld9x4rqyBSCLzsJM", "direction": "asc"}, {"fieldId": "fldulcCPDVz87Bmnw", "direction": "desc"}]
Returns:
Tool execution result
Tags:
list, records, table
"""
async with self._get_client() as client:
call_args = {}
call_args["baseId"] = baseId
call_args["tableId"] = tableId
if cursor is not None:
call_args["cursor"] = cursor
if fieldIds is not None:
call_args["fieldIds"] = fieldIds
if filters is not None:
call_args["filters"] = filters
if pageSize is not None:
call_args["pageSize"] = pageSize
if recordIds is not None:
call_args["recordIds"] = recordIds
if sort is not None:
call_args["sort"] = sort
result = await client.call_tool("list_records_for_table", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def create_records_for_table(self, baseId: str, records: list[Any], tableId: str, typecast: bool = None) -> dict[str, Any]:
"""
Creates new records in an Airtable table.
To get baseId and tableId, use the search_bases and list_tables_for_base tools first.
For singleSelect/multipleSelects 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.
Example: create a record with singleLineText, number, singleSelect, and multipleSelects fields:
{"baseId": "appZfrNIUEip5MazD", "tableId": "tblGlReoTNWfYnXIG", "records": [{"fields": {"fldGlRtkBNWfYnPOV": "Launch meeting", "fldulcCPDVz87Bmnw": 42, "fld8WsrpLHHevsnW8": "In progress", "fldgD18XtsueoiguT": ["Urgent", "Q1"]}}]}
Args:
baseId: The ID of the base containing the table.
Must start with "app" and is 14 characters long.
Example: "appZfrNIUEip5MazD".
Do not substitute user-facing names for baseId.
To get baseId, use the search_bases or list_bases tool.
records: An array of record objects to create. Each record must have a "fields" property
containing the field values. You can create up to 10 records per request.
tableId: The ID of the table to create a record in.
Must start with "tbl" and is 14 characters long.
Example: "tblGlReoTNWfYnXIG".
Do not substitute user-facing names for tableId.
To get tableId, use the list_tables_for_base tool.
typecast: Whether or not to perform best-effort automatic data conversion from string values.
Defaults to false to preserve data integrity.
Returns:
Tool execution result
Tags:
create, records, table
"""
async with self._get_client() as client:
call_args = {}
call_args["baseId"] = baseId
call_args["records"] = records
call_args["tableId"] = tableId
if typecast is not None:
call_args["typecast"] = typecast
result = await client.call_tool("create_records_for_table", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
async def update_records_for_table(self, baseId: str, records: list[Any], tableId: str, performUpsert: dict[str, Any] = None, typecast: bool = None) -> dict[str, Any]:
"""
Updates records in an Airtable table.
The fields you specify will be updated, and all other fields will be left unchanged.
To get baseId and tableId, consider using the search_bases and list_tables_for_base tools first.
For singleSelect/multipleSelects 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.
Example: update a record's fields:
{"baseId": "appZfrNIUEip5MazD", "tableId": "tblGlReoTNWfYnXIG", "records": [{"id": "recZOTa3BDHxlJNzf", "fields": {"fldGlRtkBNWfYnPOV": "Updated name", "fld8WsrpLHHevsnW8": "Done"}}]}
Args:
baseId: The ID of the base containing the table.
Must start with "app" and is 14 characters long.
Example: "appZfrNIUEip5MazD".
Do not substitute user-facing names for baseId.
To get baseId, use the search_bases or list_bases tool.
records: An array of record objects to update. Each record must have a "fields" property
containing the field values. You can update up to 10 records per request.
tableId: The ID of the table to update records in.
Must start with "tbl" and is 14 characters long.
Example: "tblGlReoTNWfYnXIG".
Do not substitute user-facing names for tableId.
To get tableId, use the list_tables_for_base tool.
performUpsert: Enables upsert behavior when set.
When upserting is enabled, the recordId parameter is optional.
Records that do not include a recordId will use the fields chosen by the fieldIdsToMergeOn parameter to match with existing records.
- If no matches are found, a new record will be created.
- If a match is found, that record will be updated.
- If multiple matches are found, the request will fail.
Records that include id will ignore fieldIdsToMergeOn and behave as normal updates.
If no record with the given id exists, the request will fail and will not create a new record
typecast: Whether or not to perform best-effort automatic data conversion from string values.
Defaults to false to preserve data integrity.
Returns:
Tool execution result
Tags:
update, records, table
"""
async with self._get_client() as client:
call_args = {}
call_args["baseId"] = baseId
call_args["records"] = records
call_args["tableId"] = tableId
if performUpsert is not None:
call_args["performUpsert"] = performUpsert
if typecast is not None:
call_args["typecast"] = typecast
result = await client.call_tool("update_records_for_table", call_args)
texts = []
for block in result.content:
if hasattr(block, "text"):
texts.append(block.text)
text = "\n".join(texts)
try:
return json.loads(text)
except (json.JSONDecodeError, TypeError):
return {"result": text}
def list_tools(self):
return [self.ping, self.list_bases, self.search_bases, self.list_tables_for_base, self.get_table_schema, self.list_records_for_table, self.create_records_for_table, self.update_records_for_table]