Grist MCP server for AI agents

Live

BEARER TOKEN

SPREADSHEET DATABASE

Productivity

Grist gives your agent spreadsheet database access across your team sites: fetch, add, update, and upsert records, run read-only SQL queries, manage tables and columns, and organize documents, workspaces, and sharing.

  • Per-user credentials: each call uses the actual user's access token, never a shared bot.
  • Encrypted per-tenant vault: AES-256, resolved at request time, never in LLM context.
  • Scoped before every call: pre-call scope check, 90-day SIEM-exportable audit chain.
Grist
agent · Acme Q3
Run
Which invoices in our Billing doc are overdue?
S
grist_run_sql_query
116ms
Data agent
11 invoices are overdue, totaling $48,300. The largest is Globex at $12,500, now 34 days late.
Sources: Billing document, 1 SQL query
grist
11 records
18:29
Message Claude...

Tools your data agent reaches for on Grist, scoped per user.

CALL ANY TOOL
48 tools for spreadsheet databases in Grist: fetch, add, update, and upsert records, run read-only SQL, create tables and columns, copy, fork, and compare documents, manage workspaces, and change access.
grist_add_columns
Add columns
Add one or more columns to an existing table. Returns a columns array with the id of each column created. Use update_columns to change existing columns. Use create_tables to make a whole new table.
Parameters
Name
Type
Required
Description
columns
array
Required
Columns to add. Each item is an object with an id (the column name) and an optional fields object such as type (Any, Text, Numeric, Int, Bool, Date, DateTime:<timezone>, Choice, ChoiceList, Ref:<tableId>, RefList:<tableId>, Attachments), label, formula, isFormula, and widgetOptions (a JSON string). Example: [{"id": "Email", "fields": {"type": "Text", "label": "Email"}}].
doc_id
string
Required
ID of the Grist document (a UUID-style string, also shown as the document's id in its URL or from Grist list workspaces results).
table_id
string
Required
Table identifier: the normalized table name shown as TABLE ID in the document's Raw Data page (for example Table1), or its numeric row ID in _grist_Tables.
grist_add_records
Add records
grist_apply_user_actions
Apply user actions
grist_compare_docs
Compare docs
grist_create_doc
Create doc
grist_create_tables
Create tables
grist_delete_column
Delete column
grist_delete_records
Delete records
grist_fork_doc
Fork doc
grist_get_attachment_metadata
Get attachment metadata
grist_get_current_user
Get current user
grist_get_doc_states
Get doc states
grist_get_org
Get org
grist_get_table_schema
Get table schema
grist_list_attachments
List attachments
grist_list_columns
List columns
grist_list_doc_snapshots
List doc snapshots
grist_list_orgs
List orgs
grist_list_records
List records
grist_list_workspace_access
List workspace access
grist_move_doc
Move doc
grist_restore_doc
Restore doc
grist_restore_workspace
Restore workspace
grist_run_sql_query
Query run sql
grist_trash_workspace
Trash workspace
grist_update_columns
Update columns
grist_update_doc
Update doc
grist_update_records
Update records
grist_update_workspace
Update workspace
grist_upsert_columns
Upsert columns

For more tools, view docs.

Build your Agent
Same auth pattern across LangChain, OpenAI, Anthropic, and Google ADK.
Python · LlamaIndex
import { ScalekitClient } from "@scalekit-sdk/node";
import { createReactAgent } from "@langchain/langgraph/prebuilt";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);

// Grist tools scoped to this user
const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["grist"], toolNames: [
    "grist_list_tables",
    "grist_run_sql_query",
    "grist_update_records"] },
  pageSize: 100,
});

const agent = createReactAgent({ llm, tools });
await agent.invoke({ messages: [{ role: "user", content: "Find overdue invoices in the Billing document and mark them for follow-up" }] });
import OpenAI from "openai";
import { ScalekitClient } from "@scalekit-sdk/node";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);
const openai = new OpenAI();

const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["grist"] }, pageSize: 100,
});

const res = await openai.chat.completions.create({
  model: "gpt-5",
  messages: [{ role: "user", content: "Find overdue invoices in the Billing document and mark them for follow-up" }],
  tools,
});

// Execute the tool call with the user's vaulted Grist access token
await sk.tools.executeTool(res.choices[0].message.tool_calls[0], "user_123");
import Anthropic from "@anthropic-ai/sdk";
import { ScalekitClient } from "@scalekit-sdk/node";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);
const anthropic = new Anthropic();

const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["grist"] }, pageSize: 100,
});

const msg = await anthropic.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Find overdue invoices in the Billing document and mark them for follow-up" }],
  tools,
});

// Tool call runs with the user's vaulted Grist access token
await sk.tools.executeTool(msg.content, "user_123");
import { Agent } from "@google/adk/agents";
import { ScalekitClient } from "@scalekit-sdk/node";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);

const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["grist"] }, pageSize: 100,
});

const agent = new Agent({
  name: "data_agent",
  model: "gemini-2.5-pro",
  instruction: "Grist tools scoped to this user",
  tools,
});

await agent.run("Find overdue invoices in the Billing document and mark them for follow-up");
Try these prompts
Copy any prompt into your agent. Each maps directly to a Grist tool. Click to copy, paste into your agent, done.
Records
Copy the prompt
Copied
List records in the Invoices table where Status is Overdue.
Copy the prompt
Copied
Add a record to Invoices for Acme, $4,200, due November 30.
Copy the prompt
Copied
Upsert these 15 customers into the Accounts table by email.
Queries and schema
Copy the prompt
Copied
Run a SQL query for total invoice amount by customer.
Copy the prompt
Copied
Show the columns and types in the Projects table.
Copy the prompt
Copied
Add a Renewal Date column to the Accounts table.
Documents and sharing
Copy the prompt
Copied
List the workspaces and documents in our team site.
Copy the prompt
Copied
Fork the Q4 Budget document so I can try changes.
Copy the prompt
Copied
Give priya@acme.com editor access to the Billing document.
SEE HOW AUTH WORKS
Each user connects their own Grist access token once, and Scalekit sends it with every call. Access tokens stay vaulted, every call is scope checked, and every action is logged.
1
Authorize
Your user connects
Grist
once. We tie it to their identity and the meetings they approved — no shared bot account, no org-wide access
Who:
user ‘A’
when:
Once per user
access:
Limited to user
2
Store
Their
Grist
token lives in a vault scoped to them. User A's meetings are never reachable by an agent acting for user B, even on the same connection
vault:
encrypted
scope:
per-user
tokens:
auto-refreshed
3
Resolve
When your agent calls a
Grist
tool, we fetch the right token server-side. It never touches your agent, never appears in the LLM context, never shows up in your logs
speed:
~40ms
check:
before every call
seen by:
nobody
4
Audit
Every
Grist
tool call is logged — who triggered it, which meeting was fetched, what came back. 90 days of history, tied to the user who authorized it
history:
90 days
export:
SIEM-ready
logged:
every call
Test other agents
See the same per-user auth pattern across other database connectors.
GTM and RevOps Teams
CRM AI agent
Reads the Granola transcript after every call, extracts next steps and updates the HubSpot record, drafts the follow-up in Gmail, and confirms in Slack, all on the rep's own delegated OAuth.
GTM and RevOps Teams
Deal room sync
Pulls opportunity context from Salesforce, captures key decisions from Slack, and syncs a running summary to the deal room doc in Google Drive, but only when the deal actually changed.
Test other agents
See the same per-user auth pattern across other database connectors.
GTM
CRM AI agent
Turn each Granola call transcript into a HubSpot record update, a drafted Gmail follow-up, and a Slack recap.
GTM
Deal room sync agent
Pull opportunity context from Salesforce, capture decisions from Slack, and keep the Google Drive deal room doc current.
Why Scalekit
Secure your agent's access. Connectors ship in minutes
01.
Shared tokens break per-user analytics
A shared Grist access token looks fine in a demo. In production every record and access change looks like one service account, and you cannot tell which user triggered it. Scalekit resolves the credential of the actual user who triggered the agent, never a shared bot.
// shared token
audit → bot_service_account

// scalekit
audit → user_abc ✓
02.
Authentication is not authorization
03.
Multi-tenancy is architectural
04.
Grist today. Ten connectors tomorrow.
“Our agents act across Salesforce, Gong, Google Drive, and more, on behalf of every customer. Scalekit behind the scenes meant we can keep adding tools without ever rebuilding how credentials or tool calling work.”
Venu Madhav Kattagoni
Head of Engineering / Von
FAQs
Frequently Asked Questions
Does the agent access Grist as the user or through a shared access token?
As the user. Each user connects their own Grist access token once, and Scalekit sends it with every call. Audit logs attribute every record, schema, and access change to that user, not a shared service account.
Where is the Grist access token stored?
In Scalekit's managed AES-256 token vault, namespaced per tenant. To rotate it, the user reconnects with a new access token. Revocation is a single dashboard action. Credentials never appear in prompts, logs, or LLM context.
Can I limit what the agent does in Grist?
Yes. Filter by tool name in listScopedTools to expose only what you want, for example grist_list_records and grist_run_sql_query without grist_delete_doc or grist_delete_workspace, which permanently remove documents. Scalekit also enforces scope checks before every API call.
What happens when a user revokes Grist access?
The connection is invalidated on the next tool call. Subsequent requests for that user fail closed with a clear error. Other users in the tenant remain unaffected. The event is logged for audit.
Can the agent run SQL against a Grist document?
Yes. grist_run_sql_query runs a read-only SQL SELECT query against the document's SQLite database, so the agent can join and aggregate across tables. Writes go through record tools such as grist_add_records and grist_update_records.
Start in your coding agent
Up and running in one command
Install the Scalekit skill in your editor of choice. Connector, auth, tools, prompt, all wired up
Claude Code REPL
/plugin marketplace add scalekit-inc/claude-code-authstack
/plugin install agentkit@scalekit-auth-stack
Cursor Code REPL
# ~/.cursor/mcp.json
{
""mcpServers"": {
""grist"": {
""url"": ""https://mcp.scalekit.com/grist"",
""headers"": { ""Authorization"": ""Bearer $SCALEKIT_TOKEN"" }
}
}
}
Codex Code REPL
# ~/.codex/config.toml
[mcp_servers.grist]
url = ""https://mcp.scalekit.com/grist""
auth_env = ""SCALEKIT_TOKEN""
Copilot Code REPL
# .vscode/mcp.json
{
""servers"": {
""grist"": {
""url"": ""https://mcp.scalekit.com/grist"",
""type"": ""http""
}
}
}