Skip to main content
URL: 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 like con_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

Call describe_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?