MCP Tools: Contacts and Accounts

Everything on this page touches Engage — the people who have filled in a form, been imported, or been created by hand, and the accounts they belong to. Two things make it different from the rest of the MCP surface. Writes here are not drafts: a contact you edit through a connected AI client is edited immediately, in live CRM data, with no publish step in between. And your plan’s contact cap silently narrows what these tools can see.

All eight tools gate on the contact:read and contact:write privileges, and their RBAC overrides must be scoped to the contact resource specifically: an override that made you an editor on a page type does not satisfy a contact gate. All eight are also available in the Leed Assistant.

Contacts

list_engage_contacts

GET /api/engage/contacts · Returns { contacts, total, totalAll, stageCounts }

ParameterTypeRequiredDefaultNotes
searchstringNo—Free-text match over first name, last name, email and title
limitinteger, 1–100No50Page size
offsetinteger, ≥ 0No0Rows to skip

Contacts come back sorted by readiness, highest first, with unscored contacts last and the contact id as a stable tiebreak so paging is consistent. Each row carries the contact’s profile, the account it is linked to, its joined engage score (or null when unscored) and an optedOut flag for anyone whose most recent opt record is a do-not-contact.

stageCounts is a four-key breakdown — awareness, consideration, evaluation, decision — of the scored contacts inside the current search, and is always present with zeros rather than omitted.

get_engage_contact

GET /api/engage/contacts/:id · Returns { contact }

ParameterTypeRequiredDefaultNotes
idintegerYes—The contact id from list_engage_contacts

A contact that does not exist, belongs to another workspace, or sits outside your plan’s visible window all return the same 404. That is deliberate: a locked contact’s existence is never confirmed.

create_contact

POST /api/leaddetails/create · Returns 201 with { leadDetailsId }

ParameterTypeRequiredDefaultNotes
emailemail addressYes—The natural key, together with your workspace
firstNamestringNo——
lastNamestringNo——
titlestringNo—Job title — also what persona matching reads
phonestringNo——
countrystringNo——

The email domain is derived server-side, and a genuinely new contact is stamped with the source manual.

update_contact

PUT /api/leaddetails/:leadDetailsId · Returns { leadDetail }

ParameterTypeRequiredDefaultNotes
leadDetailsIdintegerYes—Which contact to edit
firstNamestring or nullNounchanged—
lastNamestring or nullNounchanged—
titlestring or nullNounchanged—
phonestring or nullNounchanged—
linkedinUrlstring or nullNounchanged—
countrystring or nullNounchanged—
timezonestring or nullNounchangedValidated against the runtime’s IANA zone set; a bad value is stored as null, not rejected
leadCompaniesIdinteger or nullNounchangedThe account to link to; null detaches the contact

Email is not editable. It is the natural key, and there is no tool that changes it. An empty string clears a field, which is worth stating to a model explicitly — "" is a deletion, not a no-op.

total versus totalAll

Both counts come back on every list_engage_contacts response, and the difference is not what most readers first assume:

FieldCountsAffected by search?Affected by your contact cap?
totalContacts matching the current searchYesYes
totalAllContacts ignoring the current searchNoYes

With no search, the two are equal. totalAll is the “of how many” number for a filtered view, not an uncapped grand total:

{
  "contacts": [ "…12 rows…" ],
  "total": 12,
  "totalAll": 250,
  "stageCounts": { "awareness": 5, "consideration": 4, "evaluation": 3, "decision": 0 }
}

The window is the oldest contacts you own, ordered by creation date. So the contacts that get locked as you grow past your cap are the newest arrivals — the ones a model is most likely to be looking for.

PlanContacts visible
Free250
Starter1,000
Growth10,000
Enterpriseunlimited

Those are the launch defaults; a workspace’s own stored quota is what actually applies. Contacts covers the contact record itself, its sources, and the saved views the CMS builds on the same data.

Accounts

An account is a company: firmographics, a link to the contacts who work there, and a rolled-up score.

list_engage_accounts

GET /api/engage/accounts · Returns { accounts }

Takes no parameters. Every account for the workspace comes back with its firmographic fields and its persisted score — readiness, fit, intent and journey stage — or score: null when the account has never been scored.

update_engage_account

PUT /api/engage/accounts/:id · Returns { account }, or 404 if there is no such account

ParameterTypeRequiredDefaultNotes
idintegerYes—Which account to edit
namestringNounchangedCannot be cleared — it is the display key
industrystring or nullNounchanged—
urlstring or nullNounchanged—
sizeinteger ≥ 0, or nullNounchangedHeadcount
revenuenumber ≥ 0, or nullNounchanged—
currentCustomerbooleanNounchangedAccepts true/false, never null
sicCodesstring or nullNounchanged—
address, address2, city, state, postalCode, countrystring or nullNounchangedPostal address
purchasedProductsstring or nullNounchanged—
competitiveProductsstring or nullNounchanged—

Only the fields you send change; a nullable field accepts an explicit null to clear it. Accounts is the same record seen from the Engage screens.

currentCustomer is the Docs MCP gate

Scores

get_engage_scores

GET /api/engage/scores · Returns { score } or { scores }

ParameterTypeRequiredDefaultNotes
contactIdintegerNo—Return this contact’s score as { score }
accountIdintegerNo—Return this account’s score as { score }

Send neither and you get every score for the workspace as { scores }. A subject with no score yet returns { score: null } rather than a 404.

recompute_engage_scores

POST /api/engage/scores/recompute · Returns { recomputed } — the number of contacts scored

Takes no parameters, and it is idempotent: running it twice in a row produces the same numbers. Note that it gates on ai:write, not on contact:write like everything else on this page, so a role that can edit contacts is not automatically a role that can recompute them. The Privilege Matrix shows the minimum role that carries it.

Scores are never computed per request

A read returns whatever was last persisted. Nothing is recalculated because you asked for it, which is why a contact created a minute ago comes back with score: null rather than a freshly derived number — it has not been scored yet.

recompute_engage_scores is what changes that, and it rewrites every contact and account score for the workspace from current signals: profile completeness, email domain, source, opens and clicks, and title-to-persona matching. It is not a targeted operation, so a model should not reach for it after every small edit. One useful rule: run it after deleting or reworking a persona, because persona assignments are only rewritten by a recompute. What each of readiness, fit, intent, journey stage and persona match actually measures is in Engage Scores.

There is no delete

Contacts and accounts cannot be deleted through MCP, and there is no delete tool for either of them in the Leed Assistant either — unlike pages, forms, personas and campaigns, which have destructive tools that exist but are hidden from MCP. Nothing on this surface removes a contact record. Removal is a CMS action.

If the tool name you came here for is not on this page, the Operator MCP Tool Index lists every tool alphabetically with the page that documents its parameters.

ESC