https://mcp.chatsailer.com/mcp/crm
Read and manage a Sailer workspace. Record tools take a resource of
"contact", "organization", or "deal" so they can cover the CRM without
eating the client’s tool budget. Campaigns and conversation traces have their
own tools. There is no send on a live conversation.
A tool that spans resources is listed if you have any of its read or write
scopes, then checks the scope for the resource you named. A deals:read
connection sees search_records but cannot search contacts.
Every workspace defines its own custom fields. The JSON Schema a client caches
does not include them — clients cache by server, not by tenant. That is
why describe_schema exists, and why you call it first.
First connection is read-only. Write tools will not even appear
until you reconnect with the matching write scope —
contacts:write,
organizations:write, deals:write, or campaigns:write.Orient
whoami
No arguments, no scope. Which workspace this connection is bound to, who
authorized it, oauth vs api_token, whether it reads company-wide, and the
scopes you actually have.
Which Sailer workspace am I connected to?A Sailer account can belong to several workspaces. The connection is bound to exactly one, chosen at consent. No tool argument can change it.
describe_schema
Needs any of contacts:read, organizations:read, or deals:read. Optional
resource ("contact", "organization", "deal"); omit it to describe
everything this connection can read.
Returns built-in and custom fields, which are required or read-only, select
options, and which keys are filterable, sortable, or expandable. On deals it
also returns the workspace’s pipelines and stages — you need a valid pair to
create a deal.
What custom fields does this workspace have?Use the
key it returns inside custom_fields on create and update. Guessing
from another workspace — or from these docs — is how you get an unknown-field
error.
Read records
Ids look likecon_8f3a…, org_8f3a…, deal_8f3a…. The prefix is the
resource. get_record and update_record take no resource argument — a pair
that disagreed would have no principled winner.
search_records
Needs any of contacts:read, organizations:read, or deals:read. resource
defaults to "contact". The named resource’s own read scope is what authorizes
the call.
If
has_more is true, keep paging. Do not count a page and call it the
population — that is what count_records is for. Cursors are opaque, same
contract as REST pagination.
Find contacts created in the last 7 days.
Find deals in Negotiation.
get_record
Needs any of contacts:read, organizations:read, or deals:read. id,
optional expand. The prefix on id selects the resource.
Show me contact con_…
Show me deal deal_…
Missing, deleted, or outside what you can see: not found. Same next step in
all three cases — search again.
count_records
Needs any of contacts:read, organizations:read, or deals:read. Same
resource / filter / q as search, no paging.
How many contacts are in this workspace?
How many organizations are in this workspace?
Write records
Calldescribe_schema first. Unknown or read-only custom-field keys are
rejected, not ignored.
Required on create depends on resource:
create_record
Needs any of contacts:write, organizations:write, or deals:write. New
row. Prefer upsert_contact if the person may already exist — a duplicate is
not automatically reversible.
Create a contact named Ana with phone +5511999999999.
Create an organization named Acme.
update_record
Needs any of contacts:write, organizations:write, or deals:write. id
only — no resource argument. Destructive: it overwrites what you send. Omit
a field to leave it; send null to clear it. Read first if you meant to
append.
Set Ana’s email to ana@example.com.
upsert_contact
Scope: contacts:write. Contacts only. Match on phone, after
canonicalization — (11) 98765-4321 and +55 11 98765-4321 are the same
person. Default on_match is update; ignore returns the existing row
untouched.
The result tells you which branch ran (created vs matched_existing). The
record alone does not.
Add or update the contact with this phone number.
Deals
move_deal_stage
Scope: deals:write. deal_id, plus stage_name or stage_id.
Give stage_name — the name as it appears on the board, matched
case-insensitively — and it is resolved within this deal’s own pipeline.
You cannot accidentally move a deal into another pipeline’s stage this way.
Use stage_id only if you already have one.
Move Acme to Negotiation.
Campaigns
list_campaigns
Scope: campaigns:read. Newest first. Optional status, limit 1–100
(default 25). Use this to get a campaign_id.
List this workspace’s campaigns.
get_campaign
Scope: campaigns:read. One campaign plus a page of its participants.
Optional participant_limit (default 25; 0 for the campaign alone).
projected_send_at is an estimate, not a commitment.
For aggregate performance, call get_campaign_analytics instead — this
returns rows, not totals.
Who is in this campaign, and where does each contact stand?
get_campaign_analytics
Scope: campaigns:read. One campaign, one metric family.
Optional
window_start / window_end (UTC). Defaults to the last 30 days.
These families are not interchangeable. big_numbers and daily_metrics
use a strict “answered” (a qualifying inbound, classified as a reply).
status_funnel uses the lifecycle status, which is usually a larger number.
Rates on big_numbers are 0–100, not fractions.
Every response carries _meta.caveats. Read them before comparing two
numbers. A difference between two families is not a change over time.
Show me campaign performance. Read the caveats before you summarize.
add_campaign_participants
Scope: campaigns:write. Destructive. Sends real messages to real people.
Confirm with the user before calling.
Enrolling a contact in a running campaign means the campaign will message them
on its own schedule — a WhatsApp message to an actual phone number. That
cannot be recalled once sent.
The contacts must already exist; this does not create them. Use
search_records or upsert_contact first. Anyone already in the campaign is
reported in already_present and is not enrolled twice. There is no way to
make a campaign send immediately from here.
Add these contacts to the campaign. Confirm with me before you call it.
Conversations
There is no tool that lists conversations or sends a message on one. REST can list and retrieve them; a model reaches a conversation from the contact it is already working with. Sending on a live conversation is unwired on purpose — use the Studio sandbox if you need to test a turn.get_conversation_trace
Scopes: conversations:read and inference:read. conversation_id, optional
since.
Explains why the agent routed a real conversation the way it did: every
routing edge it considered, which conditions passed, and the values they
compared. The useful part is usually the edges that lost. Pass since to
scope this to one turn — without it, a long thread returns a great deal of
history.
This does not include prompts, model completions, model names, token counts,
or cost.
Why did the agent say that to this customer?