Skip to content

Agents

Scopes & tools

The nine agent scopes, the MCP tools they unlock, and how writes, retries and approvals behave.

Scopes are resource:action pairs and deny by default. An agent holds exactly the pairs it was created with; the presets in the console are shortcuts that expand to these pairs and are never stored as a level of their own.

Scopes

ScopeGrantsRead-onlyAssistantOperator
contacts:readRead contacts✓✓✓
contacts:writeEdit contact fields✓
deals:readRead deals✓✓✓
deals:writeMove deals between stages✓
tasks:readRead tasks✓✓✓
tasks:writeCreate and update tasks✓✓
notes:writeAdd notes✓✓
activity:readRead recent activity✓✓✓
email:draftDraft emails for review✓✓

There is no email:send scope. Agents draft; a person sends.

Tools

tools/list returns only the tools the agent's scopes allow, always in the order below, so a client can cache the list. Calling a tool outside the agent's scopes gets the same -32602 Unknown tool error as a tool that does not exist, so a client cannot probe for what it was not given.

ToolScopeKindWhat it does
search_contactscontacts:readreadFind contacts by name, email or company. Returns ids to use with other tools.
get_contactcontacts:readreadRead one contact by id, with its recent activity.
list_dealsdeals:readreadList deals, optionally filtered by stage or contact.
count_bycontacts:readreadCount contacts, deals or tasks grouped by a field.
list_taskstasks:readreadList tasks, optionally filtered by status or assignee.
recent_activityactivity:readreadRead the recent activity log for the organisation.
create_notenotes:writewriteAdd a note, optionally linked to a contact or deal.
create_tasktasks:writewriteCreate a task with an optional due date.
update_tasktasks:writewriteChange a task's title or status.
update_contactcontacts:writewriteEdit a contact's editable fields.
move_dealdeals:writewriteMove a deal to another stage.
draft_follow_up_emailemail:draftwritePropose a follow-up email to one contact for a person to review and send.
ack_eventnoneevent stateConfirm receipt of a webhook event so Clivly stops redelivering it. Takes eventId. Does not lift the review hold after a visitor message.

count_by is offered to any agent holding contacts:read, but it checks the grant for the dataset you ask about when it runs: an agent with only contacts:read can count contacts and is refused for deals or tasks.

Annotations

Every tool publishes MCP annotations so your client can decide what to confirm with its user:

  • Reads are readOnlyHint: true and idempotentHint: true.
  • Writes are readOnlyHint: false. update_task, update_contact and move_deal are idempotentHint: true — setting the same value twice lands on the same state. create_note, create_task and draft_follow_up_email are idempotentHint: false, because a second call would be a second record.
  • No tool is destructiveHint: true. No agent tool deletes anything.

Annotations are hints for the client, not the security boundary. Scope checks on the server are the boundary.

Write tool inputs

Every write takes an idempotencyKey (8–200 characters) alongside its own fields. Ids are up to 64 characters of letters, digits, _ and -.

ToolFields
create_notetitle (1–200), body (1–10,000), optional contact and/or deal
create_tasktitle (1–200), optional dueAt (ISO 8601 date-time), optional contact and/or deal
update_taskthe task, optional title (1–200), optional status: todo, in_progress, done or cancelled
update_contactthe contact, optional phone (up to 50), status (up to 50), title (up to 200)
move_dealthe deal, stage (up to 100)
draft_follow_up_emailthe contact, subject (1–200), body (1–10,000)

Naming records

Each record a write touches can be given by id or the way a person would name it. Pass one or the other, never both:

RecordBy idBy name
ContactcontactIdcontact: a full or partial name, or an email
DealdealIddeal: its title
TasktaskIdtask: its title

So a user can say "add a note to Ada Lovelace" and the agent passes contact: "Ada Lovelace" without looking anything up first. Clivly matches from the most specific to the least: id, then email, then exact name (ignoring case), then part of the name. Archived and merged contacts are never matched.

  • One match: the write goes ahead on that record.
  • No match: an error suggests the read tool to search with.
  • Several matches: nothing is written. The result is an error that lists up to five candidates, each with an email, company, stage or status to tell them apart, and its id. The agent should ask its user which one they mean, then call again with that id. This costs nothing against the write limit.

Naming a record needs the matching read scope (contacts:read, deals:read or tasks:read), because the candidates show names and emails. Without it, pass the id. A proposal waiting for approval stores the id the name resolved to, so approving it later always changes the record the agent meant.

update_contact refuses fields synced from your app. On a synced contact those stay as your app wrote them — change them in your app and they update on the next sync.

Idempotency and retries

A write must carry an idempotencyKey. Use a fresh key for each change you intend, and the same key when you retry that change.

  • A retry with a key that already succeeded replays the first result. It does not write again, and it does not count as a second change.
  • A retry while the first attempt is still running is refused with An identical call is already in progress. Retry again with the same key.
  • A call that failed releases its key, so retrying the same key runs it again.

Keys are scoped to one agent and one tool, so two agents — or two different tools — never collide on the same key.

Results

A write's outcome is one of three shapes. On success or a pending approval, the tools/call result carries it as structuredContent, with a readable summary in content. An error comes back as a tool result with isError: true and the reason as text, so the model can read it and adjust.

write-result.json
{ "status": "ok", "data": { ... } }

{ "status": "pending_approval", "approvalId": "…", "summary": "…" }

{ "status": "error", "error": "…" }

What needs approval

These writes are not made directly. They are recorded as a proposal and return pending_approval with an approvalId:

  • move_deal to a Won or Lost stage. Moves between other stages are direct.
  • Every draft_follow_up_email. The draft is the proposal; a person reviews and sends it.
  • Every write while a visitor message is outstanding for this agent — see Limits & security.

A person approves or rejects the proposal under Ask your CRM. Approving applies the change with the arguments the agent sent, re-running every check except the approval rule; if it no longer applies, the proposal stays pending with the reason. See Review what agents propose.

Retrying a call that went to approval replays the same approvalId rather than queueing a second proposal. To learn what became of an email, subscribe to draft.decided.

Deal stages

stage is a stage key from the deal's own pipeline. Any other key is refused with Choose a stage from this deal's pipeline. Whether a stage counts as Won or Lost comes from the stage's type, not its label, so renaming "Won" does not let a move past the approval rule. On a deal with no pipeline, the keys won and lost are the terminal ones.

Email drafts

draft_follow_up_email writes to one contact, who must have an email address, and the subject must be a single line. A draft whose subject or body contains a link, or an email address other than the recipient's, is refused rather than queued: an agent that has read hostile record text must not be able to slip a link or another address into a message a busy person approves.

Approving a draft proposal does not send it. It becomes a pending draft in the approver's own queue, which they edit and send.