Twenty CRM MCP server for AI agents

Live

BEARER TOKEN

CRM

CRM & Sales

Twenty CRM gives your agent CRM work in your Twenty workspace: create, update, and search people, companies, and opportunities, log notes and tasks, and read the workspace data model and members.

  • 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.
Twenty CRM
agent · Acme Q3
Run
Which opportunities close this month?
S
twenty_list_opportunities
101ms
CRM agent
5 opportunities close this month, worth $212K combined. The largest is the Contoso renewal at $80K.
Sources: Twenty, 5 opportunities
twenty
5 opportunities
18:29
Message Claude...

Tools your CRM agent reaches for on Twenty CRM, scoped per user.

CALL ANY TOOL
30 tools for CRM in Twenty: list, create, update, and delete people, companies, opportunities, notes, and tasks, and read the workspace data model, custom fields, and members.
twenty_create_company
Create company
Create a new company record in Twenty. Every field is optional, so send only the values to set. Returns data.createCompany with the new record, including its id. Use twenty_create_company for a new record. Use twenty_update_company to change an existing one.
Parameters
Name
Type
Required
Description
account_owner_id
string
Optional
UUID of the workspace member who owns the account. Use twenty_list_workspace_members to find it.
address_city
string
Optional
City of the company address.
address_country
string
Optional
Country of the company address.
address_postcode
string
Optional
Postal code of the company address.
address_state
string
Optional
State or region of the company address.
address_street1
string
Optional
First line of the street address.
arr_amount_micros
number
Optional
Annual recurring revenue in micros, where 1,000,000 micros equals one currency unit.
arr_currency_code
string
Optional
ISO 4217 currency code for the annual recurring revenue.
depth
integer
Optional
Level of related records to include in the response. 0 returns only the record, 1 also includes its direct relations. One of: `0`, `1`. Default: `1`.
domain_url
string
Optional
Company website URL.
employees
integer
Optional
Number of employees.
ideal_customer_profile
boolean
Optional
Whether the company matches the ideal customer profile.
linkedin_url
string
Optional
URL of the company's LinkedIn page.
name
string
Optional
Company name.
x_url
string
Optional
URL of the company's X (Twitter) profile.
twenty_create_note
Create note
twenty_create_opportunity
Create opportunity
twenty_create_person
Create person
twenty_create_task
Create task
twenty_delete_company
Delete company
twenty_delete_note
Delete note
twenty_delete_opportunity
Delete opportunity
twenty_delete_person
Delete person
twenty_delete_task
Delete task
twenty_get_company
Get company
twenty_get_note
Get note
twenty_get_object
Get object
twenty_get_opportunity
Get opportunity
twenty_get_person
Get person
twenty_get_task
Get task
twenty_get_workspace_member
Get workspace member
twenty_list_companies
List companies
twenty_list_fields
List fields
twenty_list_notes
List notes
twenty_list_objects
List objects
twenty_list_opportunities
List opportunities
twenty_list_people
List people
twenty_list_tasks
List tasks
twenty_list_workspace_members
List workspace members
twenty_update_company
Update company
twenty_update_note
Update note
twenty_update_opportunity
Update opportunity
twenty_update_person
Update person
twenty_update_task
Update task
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);

// Twenty CRM tools scoped to this user
const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["twenty"], toolNames: [
    "twenty_list_opportunities",
    "twenty_update_opportunity",
    "twenty_create_task"] },
  pageSize: 100,
});

const agent = createReactAgent({ llm, tools });
await agent.invoke({ messages: [{ role: "user", content: "Move the Fabrikam opportunity to Proposal and create a follow-up task" }] });
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: ["twenty"] }, pageSize: 100,
});

const res = await openai.chat.completions.create({
  model: "gpt-5",
  messages: [{ role: "user", content: "Move the Fabrikam opportunity to Proposal and create a follow-up task" }],
  tools,
});

// Execute the tool call with the user's vaulted Twenty CRM 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: ["twenty"] }, pageSize: 100,
});

const msg = await anthropic.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Move the Fabrikam opportunity to Proposal and create a follow-up task" }],
  tools,
});

// Tool call runs with the user's vaulted Twenty CRM 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: ["twenty"] }, pageSize: 100,
});

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

await agent.run("Move the Fabrikam opportunity to Proposal and create a follow-up task");
Try these prompts
Copy any prompt into your agent. Each maps directly to a Twenty CRM tool. Click to copy, paste into your agent, done.
Records
Copy the prompt
Copied
Find people at contoso.com.
Copy the prompt
Copied
Create a company record for Northwind with domain northwind.io.
Copy the prompt
Copied
Update Jordan Lee's job title to VP of Sales.
Pipeline
Copy the prompt
Copied
Which opportunities close this month?
Copy the prompt
Copied
Create a $40,000 opportunity for Contoso.
Copy the prompt
Copied
Move the Fabrikam opportunity to the Proposal stage.
Notes and tasks
Copy the prompt
Copied
Create a note summarizing today's Contoso call.
Copy the prompt
Copied
Create a task to send the Northwind proposal by Friday.
Copy the prompt
Copied
List the custom fields on the opportunity object.
SEE HOW AUTH WORKS
Each user connects their own Twenty CRM access token once, and Scalekit sends it with every call. Tokens stay vaulted, every call is scope checked, and every action is logged.
1
Authorize
Your user connects
Twenty CRM
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
Twenty CRM
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
Twenty CRM
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
Twenty CRM
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 CRM and sales 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 intelligence agent
Pulls recent Gong calls, scores deal risk with an LLM, cross-references the record in Attio, and DMs each owner their at-risk deals in Slack. Every read is scoped to that rep's own access.
GTM and RevOps Teams
Sales call prep agent
Reads tomorrow's calls from Google Calendar, mines past Granola notes and Attio history for context, and delivers each rep a prep brief in Slack, scoped to the calls that rep actually owns.
Test other agents
See the same per-user auth pattern across other CRM and sales connectors.
GTM
CRM AI agent
Turn each Granola call transcript into a HubSpot record update, a drafted Gmail follow-up, and a Slack recap.
SALES
Deal intelligence agent
Score Gong call risk with an LLM, cross-reference the Attio record, and DM each owner their at-risk deals in Slack.
SALES
Sales call prep agent
Read tomorrow's calls from Google Calendar, mine Granola notes and Attio history, and deliver each rep a prep brief in Slack.
Why Scalekit
Secure your agent's access. Connectors ship in minutes
01.
Shared tokens break per-user analytics
A shared Twenty CRM access token looks fine in a demo. In production every record 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.
Twenty CRM 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 Twenty CRM as the user or through a shared access token?
As the user. Each user connects their own Twenty CRM access token once, and Scalekit sends it with every call. Audit logs attribute every record change to that user, not a shared service account.
Where is the Twenty CRM 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 Twenty CRM?
Yes. Filter by tool name in listScopedTools to expose only what you want, for example list and create tools without delete tools such as twenty_delete_person. The delete tools remove a record permanently unless soft_delete is true, which moves it to the trash. Scalekit also enforces scope checks before every API call.
What happens when a user revokes Twenty CRM 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.
Does the agent see custom fields in Twenty?
Yes. twenty_list_objects and twenty_list_fields return the workspace data model, including custom fields, so the agent knows which fields exist before it creates or updates people, companies, opportunities, notes, and tasks.
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"": {
""twenty"": {
""url"": ""https://mcp.scalekit.com/twenty"",
""headers"": { ""Authorization"": ""Bearer $SCALEKIT_TOKEN"" }
}
}
}
Codex Code REPL
# ~/.codex/config.toml
[mcp_servers.twenty]
url = ""https://mcp.scalekit.com/twenty""
auth_env = ""SCALEKIT_TOKEN""
Copilot Code REPL
# .vscode/mcp.json
{
""servers"": {
""twenty"": {
""url"": ""https://mcp.scalekit.com/twenty"",
""type"": ""http""
}
}
}