MCP server
Every Rowflare instance serves a Model Context Protocol endpoint at https://<your instance>/mcp. It uses the Streamable HTTP transport and authenticates with personal access tokens, so any MCP client that can send a header can read and write your bases, create tables and fields, and ask for grouped totals without downloading every record.
Create a token
Open Settings, API tokens and create a token. It acts as you: in workspaces where you are a viewer, only the read tools succeed; write and schema tools return a “read-only access” error. Delete the token to revoke access.
Connect a client
Claude Code:
claude mcp add --transport http rowflare https://rowflare.example.com/mcp --header "Authorization: Bearer $ROWFLARE_TOKEN"
Cursor (~/.cursor/mcp.json):
{ "mcpServers": { "rowflare": { "url": "https://rowflare.example.com/mcp", "headers": { "Authorization": "Bearer rf_..." } } } }
Claude Desktop (claude_desktop_config.json, through the mcp-remote bridge):
{ "mcpServers": { "rowflare": { "command": "npx", "args": ["-y", "mcp-remote", "https://rowflare.example.com/mcp", "--header", "Authorization: Bearer rf_..."] } } }
Tools
Tables, fields, views and select options are addressed by name (case-insensitive) or id. Records come back in the same shape as the REST API: { id, createdTime, fields } keyed by field name.
| Tool | What it does | Destructive |
|---|---|---|
list_bases |
Bases you can access, with workspace and permission | |
describe_base |
Tables, fields (type and options), primary field, row counts and views | |
query_records |
Filter (structured or Airtable formula), sort, view, field subset, paging | |
get_records |
Records by id | |
aggregate |
count, countUnique, countEmpty, countFilled, sum, avg, min, max, grouped by up to 2 fields (dates by day, week, month or year, in timezone when given) |
|
create_records |
Create records; select options and linked records can be given by name | |
update_records |
Change fields of existing records | |
delete_records |
Delete records | yes |
create_table |
New table with any field types, including links, lookups, rollups and formulas in one call | |
update_table |
Rename, change the primary field | |
delete_table |
Delete a table and the links that point at it | yes |
create_field |
Add a field | |
update_field |
Rename, change type, options or description; rename a select option with { "name": "New", "renameFrom": "Old" } |
yes (options left out of a select list are cleared from cells) |
delete_field |
Delete a field and its values | yes |
create_view |
Grid, kanban, calendar, timeline, gallery or form view with filter, sort, hidden fields, grouping (up to 3 levels in grids), date fields, lanes and card cover | |
update_view |
Change a view’s name, filter, sort, hidden fields or type-specific settings | |
list_dashboards |
Dashboards of a base with their widget counts | |
get_dashboard |
A dashboard’s widgets with their settings (table and field names) and each widget’s current numbers over all time | |
create_dashboard |
New dashboard, optionally with widgets | |
update_dashboard |
Rename a dashboard; add, change, move or remove widgets | yes (remove deletes widgets) |
list_automations |
Automations of a base with their trigger type, whether they are on, and their last run (editors and owners) | |
get_automation |
An automation’s trigger and steps with table and field names, its webhook URL, and its last 20 runs | |
create_automation |
New automation from a trigger and steps; starts switched off unless enabled is true |
|
update_automation |
Rename, switch on or off, or replace the trigger or the steps | yes (replaced steps are gone) |
list_ai_services |
The workspace’s AI services (name, kind, model), for AI fields and AI automation steps | |
generate_ai_field |
Queue AI generation for some records, the records never generated (missingOnly), or the whole column of an AI field |
AI fields
create_table, create_field and update_field take an ai option on text, longText, singleSelect, multiSelect, number, date and checkbox fields: { "service": "DeepSeek", "prompt": "Summarize {Notes} in one sentence", "model"?, "imageFields"?: ["Photo"], "autoUpdate"?: true }. The service is a name or id from list_ai_services; the prompt refers to fields by name in braces. describe_base shows the same shape. Values are generated in the background: new records and changed inputs queue automatically, and generate_ai_field fills existing records.
Dashboard widgets
A widget in create_dashboard and update_dashboard has a type (metric, bar, line, pie, pivot, text) and these settings, all by name:
- Source:
table,view(its filter applies),filter(same shape asquery_records),dateField(the field the dashboard’s date range applies to). metric:{ fn, field }withfnone of count, countUnique, countEmpty, countFilled, sum, avg, min, max;countneeds no field.compareon a metric card shows the change against the previous period.- Bar and line:
x(category or date field),bucket(day, week, month or year for a datex; month by default),series,stacked,horizontal(bar),area(line),sort(valueorkey),limit. - Pie:
group,bucket,donut,limit(default 10; the rest is shown as “Other”). - Pivot:
rows,columns,rowBucket,columnBucket. - Text:
text(Markdown, at most 20 KB). title, andposition{ x, y, w, h }on a 12-column grid (wandh1 to 12). Without a position a widget goes below the others.
update items carry the widget id from get_dashboard and change only the settings they name. get_dashboard returns each widget’s numbers (at most 100 groups per widget) and a problem when a widget’s table, view or field was deleted or is not set up yet.
Automations
Triggers and steps name tables and fields, the same as the other tools:
- Triggers:
recordCreated,recordUpdated(watchlimits it to some fields),recordMatches(fires when a record starts to matchfilter),schedule(everyhour, day, week or month,at,weekday,monthday,timezone),dateReached(dateField,offsetMinutes,at,timezone),webhookandbutton(viewerCanRun). - Steps run in order:
createRecord,updateRecords(targetistriggeror the id of an earlier find or create step),findRecords(filter,sort,limit),http(method,url,headers,body),email(providersystem, resend or smtp,to,cc,subject,body,format),ai(serviceid fromlist_ai_services,model,prompt,outputtext or json; the answer is{{steps.<id>.text}}or{{steps.<id>.json.<key>}}) andbranch(match,conditions,then,else, one level). - Templates use field names:
{{trigger.record.Name}},{{trigger.record}}(the record itself, for link fields),{{trigger.body.email}}(webhooks),{{steps.s1.records[0].Name}},{{steps.s1.count}},{{steps.s2.body.id}},{{now}},{{record.url}}. They are stored with field ids, so renaming a field does not break them. - Steps get ids
s1,s2, … in order unless you give them one. - New automations start switched off unless
enabledis true. No tool runs an automation; buttons keep their own permission.
{
"baseId": "app...",
"name": "Won deals",
"trigger": { "type": "recordMatches", "table": "Deals", "filter": { "conditions": [{ "field": "Stage", "op": "eq", "value": "Won" }] } },
"steps": [
{ "type": "http", "method": "POST", "url": "https://example.com/won?deal={{trigger.record.Name}}", "headers": { "X-Source": "rowflare" } },
{ "type": "createRecord", "table": "Companies", "values": { "Name": "From {{trigger.record.Name}}" } }
]
}
Writes use typecast by default: unknown select options are created and linked records are matched by their primary field (and created if missing), the same as the REST API with typecast: true.
Limits
query_records: 50 records per call by default, at most 200; usenextOffsetto page.get_records: 1 to 100 ids.create_records,update_records,delete_records: 1 to 200 records per call.- Filters: each operator is checked against the field type (for example
containsis rejected on a single select; useanyOf), value lists hold at most 50 items, and link fields filter by record id or by name withcontains. aggregate: up to 2 group fields, 1 to 10 metrics, at most 1,000 groups (the result saystruncatedwhen there were more).- Dashboards: at most 30 widgets each.
- Automations: at most 50 per base and 25 steps each.
- Long text values are cut at 2,000 characters in results;
get_recordswithfullText: truereturns them whole. - Lists in one cell (lookups, links, multiple selects) are cut at 100 items, and one answer holds about 150,000 characters of records:
query_recordsthen returns fewer records withnextOffset, andget_recordslists the ids that did not fit inomitted.
Not yet supported
Signing in with OAuth, which the claude.ai and ChatGPT web connectors require, is planned. Until then, use a client that lets you set the Authorization header.